comparison mxtool/mx.py @ 4158:f3a50640333b

Added support for specifying a timeout when running an external command.
author Doug Simon <doug.simon@oracle.com>
date Thu, 22 Dec 2011 22:51:37 +0100
parents c78bace5086a
children 8c507a8dd6a4
comparison
equal deleted inserted replaced
4157:b26279781d95 4158:f3a50640333b
85 # 85 #
86 # Other properties can be specified for projects and libraries for use by extension commands. 86 # Other properties can be specified for projects and libraries for use by extension commands.
87 # 87 #
88 # Values can use environment variables with Bash syntax (e.g. ${HOME}). 88 # Values can use environment variables with Bash syntax (e.g. ${HOME}).
89 89
90 import sys 90 import sys, os, errno, time, subprocess, shlex, types, urllib2, contextlib, StringIO, zipfile
91 import os 91 import shutil, fnmatch, re, xml.dom.minidom
92 import subprocess
93 from collections import Callable 92 from collections import Callable
94 from threading import Thread 93 from threading import Thread
95 from argparse import ArgumentParser, REMAINDER 94 from argparse import ArgumentParser, REMAINDER
96 from os.path import join, dirname, exists, getmtime, isabs, expandvars, isdir 95 from os.path import join, dirname, exists, getmtime, isabs, expandvars, isdir
97 import shlex
98 import types
99 import urllib2
100 import contextlib
101 import StringIO
102 import zipfile
103 import shutil, fnmatch, re, xml.dom.minidom
104 96
105 DEFAULT_JAVA_ARGS = '-ea -Xss2m -Xmx1g' 97 DEFAULT_JAVA_ARGS = '-ea -Xss2m -Xmx1g'
106 98
107 _projects = dict() 99 _projects = dict()
108 _libs = dict() 100 _libs = dict()
460 self.add_argument('--J', dest='java_args', help='Java VM arguments (e.g. --J @-dsa)', metavar='@<args>', default=DEFAULT_JAVA_ARGS) 452 self.add_argument('--J', dest='java_args', help='Java VM arguments (e.g. --J @-dsa)', metavar='@<args>', default=DEFAULT_JAVA_ARGS)
461 self.add_argument('--Jp', action='append', dest='java_args_pfx', help='prefix Java VM arguments (e.g. --Jp @-dsa)', metavar='@<args>', default=[]) 453 self.add_argument('--Jp', action='append', dest='java_args_pfx', help='prefix Java VM arguments (e.g. --Jp @-dsa)', metavar='@<args>', default=[])
462 self.add_argument('--Ja', action='append', dest='java_args_sfx', help='suffix Java VM arguments (e.g. --Ja @-dsa)', metavar='@<args>', default=[]) 454 self.add_argument('--Ja', action='append', dest='java_args_sfx', help='suffix Java VM arguments (e.g. --Ja @-dsa)', metavar='@<args>', default=[])
463 self.add_argument('--user-home', help='users home directory', metavar='<path>', default=os.path.expanduser('~')) 455 self.add_argument('--user-home', help='users home directory', metavar='<path>', default=os.path.expanduser('~'))
464 self.add_argument('--java-home', help='JDK installation directory (must be JDK 6 or later)', metavar='<path>') 456 self.add_argument('--java-home', help='JDK installation directory (must be JDK 6 or later)', metavar='<path>')
457 self.add_argument('--timeout', help='Timeout (in seconds) for subprocesses', type=int, default=0, metavar='<secs>')
465 458
466 def _parse_cmd_line(self, args=None): 459 def _parse_cmd_line(self, args=None):
467 if args is None: 460 if args is None:
468 args = sys.argv[1:] 461 args = sys.argv[1:]
469 462
504 return _java 497 return _java
505 498
506 def run_java(args, nonZeroIsFatal=True, out=None, err=None, cwd=None): 499 def run_java(args, nonZeroIsFatal=True, out=None, err=None, cwd=None):
507 return run(java().format_cmd(args), nonZeroIsFatal=nonZeroIsFatal, out=out, err=err, cwd=cwd) 500 return run(java().format_cmd(args), nonZeroIsFatal=nonZeroIsFatal, out=out, err=err, cwd=cwd)
508 501
509 def run(args, nonZeroIsFatal=True, out=None, err=None, cwd=None): 502 def _waitWithTimeout(process, args, timeout):
503 def _waitpid(pid):
504 while True:
505 try:
506 return os.waitpid(pid, os.WNOHANG)
507 except OSError, e:
508 if e.errno == errno.EINTR:
509 continue
510 raise
511
512 end = time.time() + timeout
513 delay = 0.0005
514 while True:
515 (pid, _) = _waitpid(process.pid)
516 if pid == process.pid:
517 return process.wait()
518 remaining = end - time.time()
519 if remaining <= 0:
520 process.kill()
521 abort('Process timed out after {0} seconds: {1}'.format(timeout, ' '.join(args)))
522 delay = min(delay * 2, remaining, .05)
523 time.sleep(delay)
524
525 def run(args, nonZeroIsFatal=True, out=None, err=None, cwd=None, timeout=None):
510 """ 526 """
511 Run a command in a subprocess, wait for it to complete and return the exit status of the process. 527 Run a command in a subprocess, wait for it to complete and return the exit status of the process.
512 If the exit status is non-zero and `nonZeroIsFatal` is true, then the program is exited with 528 If the exit status is non-zero and `nonZeroIsFatal` is true, then mx is exited with
513 the same exit status. 529 the same exit status.
514 Each line of the standard output and error streams of the subprocess are redirected to the 530 Each line of the standard output and error streams of the subprocess are redirected to the
515 provided out and err functions if they are not None. 531 provided out and err functions if they are not None.
516 """ 532 """
517 533
520 assert isinstance(arg, types.StringTypes), 'argument is not a string: ' + str(arg) 536 assert isinstance(arg, types.StringTypes), 'argument is not a string: ' + str(arg)
521 537
522 if _opts.verbose: 538 if _opts.verbose:
523 log(' '.join(args)) 539 log(' '.join(args))
524 540
541 if timeout is None and _opts.timeout != 0:
542 timeout = _opts.timeout
543
525 try: 544 try:
526 if out is None and err is None: 545 if out is None and err is None and timeout is None:
527 retcode = subprocess.call(args, cwd=cwd) 546 retcode = subprocess.call(args, cwd=cwd)
528 else: 547 else:
529 def redirect(stream, f): 548 def redirect(stream, f):
530 for line in iter(stream.readline, ''): 549 for line in iter(stream.readline, ''):
531 f(line) 550 f(line)
537 t.start() 556 t.start()
538 if err is not None: 557 if err is not None:
539 t = Thread(target=redirect, args=(p.stderr, err)) 558 t = Thread(target=redirect, args=(p.stderr, err))
540 t.daemon = True # thread dies with the program 559 t.daemon = True # thread dies with the program
541 t.start() 560 t.start()
542 retcode = p.wait() 561 if timeout is None:
562 retcode = p.wait()
563 else:
564 if get_os() == 'windows':
565 abort('Use of timeout not (yet) supported on Windows')
566 retcode = _waitWithTimeout(p, args, timeout)
543 except OSError as e: 567 except OSError as e:
544 log('Error executing \'' + ' '.join(args) + '\': ' + str(e)) 568 log('Error executing \'' + ' '.join(args) + '\': ' + str(e))
545 if _opts.verbose: 569 if _opts.verbose:
546 raise e 570 raise e
547 abort(e.errno) 571 abort(e.errno)