comparison mxtool/mx.py @ 4241:8fece0287975

Made command timeout also kill any currently executing subprocess (group).
author Doug Simon <doug.simon@oracle.com>
date Fri, 06 Jan 2012 22:11:20 +0100
parents 676feaf8adee
children 3e25132be4b4
comparison
equal deleted inserted replaced
4240:394424b7df1a 4241:8fece0287975
509 return _java 509 return _java
510 510
511 def run_java(args, nonZeroIsFatal=True, out=None, err=None, cwd=None): 511 def run_java(args, nonZeroIsFatal=True, out=None, err=None, cwd=None):
512 return run(java().format_cmd(args), nonZeroIsFatal=nonZeroIsFatal, out=out, err=err, cwd=cwd) 512 return run(java().format_cmd(args), nonZeroIsFatal=nonZeroIsFatal, out=out, err=err, cwd=cwd)
513 513
514 def _kill_process_group(pid):
515 pgid = os.getpgid(pid)
516 try:
517 os.killpg(pgid, signal.SIGKILL)
518 return True
519 except:
520 log('Error killing subprocess ' + str(pgid) + ': ' + str(sys.exc_info()[1]))
521 return False
522
514 def _waitWithTimeout(process, args, timeout): 523 def _waitWithTimeout(process, args, timeout):
515 def _waitpid(pid): 524 def _waitpid(pid):
516 while True: 525 while True:
517 try: 526 try:
518 return os.waitpid(pid, os.WNOHANG) 527 return os.waitpid(pid, os.WNOHANG)
536 (pid, status) = _waitpid(process.pid) 545 (pid, status) = _waitpid(process.pid)
537 if pid == process.pid: 546 if pid == process.pid:
538 return _returncode(status) 547 return _returncode(status)
539 remaining = end - time.time() 548 remaining = end - time.time()
540 if remaining <= 0: 549 if remaining <= 0:
541 process.kill()
542 abort('Process timed out after {0} seconds: {1}'.format(timeout, ' '.join(args))) 550 abort('Process timed out after {0} seconds: {1}'.format(timeout, ' '.join(args)))
551 _kill_process_group(process.pid)
543 delay = min(delay * 2, remaining, .05) 552 delay = min(delay * 2, remaining, .05)
544 time.sleep(delay) 553 time.sleep(delay)
554
555 # Makes the current subprocess accessible to the timeout alarm handler
556 # This is a tuple of the process object and args.
557 _currentSubprocess = None
545 558
546 def run(args, nonZeroIsFatal=True, out=None, err=None, cwd=None, timeout=None): 559 def run(args, nonZeroIsFatal=True, out=None, err=None, cwd=None, timeout=None):
547 """ 560 """
548 Run a command in a subprocess, wait for it to complete and return the exit status of the process. 561 Run a command in a subprocess, wait for it to complete and return the exit status of the process.
549 If the exit status is non-zero and `nonZeroIsFatal` is true, then mx is exited with 562 If the exit status is non-zero and `nonZeroIsFatal` is true, then mx is exited with
557 assert isinstance(arg, types.StringTypes), 'argument is not a string: ' + str(arg) 570 assert isinstance(arg, types.StringTypes), 'argument is not a string: ' + str(arg)
558 571
559 if _opts.verbose: 572 if _opts.verbose:
560 log(' '.join(args)) 573 log(' '.join(args))
561 574
562 if timeout is None and _opts.timeout != 0: 575 if timeout is None and _opts.ptimeout != 0:
563 timeout = _opts.timeout 576 timeout = _opts.ptimeout
564 577
578 global _currentSubprocess
579
565 try: 580 try:
581 # On Unix, the new subprocess should be in a separate group so that a timeout alarm
582 # can use os.killpg() to kill the whole subprocess group
583 preexec_fn = os.setsid if get_os() != 'windows' else None
584
566 if not callable(out) and not callable(err) and timeout is None: 585 if not callable(out) and not callable(err) and timeout is None:
567 retcode = subprocess.call(args, cwd=cwd) 586 # The preexec_fn=os.setsid
587 p = subprocess.Popen(args, cwd=cwd, preexec_fn=preexec_fn)
588 _currentSubprocess = (p, args)
589 retcode = p.wait()
568 else: 590 else:
569 def redirect(stream, f): 591 def redirect(stream, f):
570 for line in iter(stream.readline, ''): 592 for line in iter(stream.readline, ''):
571 f(line) 593 f(line)
572 stream.close() 594 stream.close()
573 stdout=out if not callable(out) else subprocess.PIPE 595 stdout=out if not callable(out) else subprocess.PIPE
574 stderr=err if not callable(err) else subprocess.PIPE 596 stderr=err if not callable(err) else subprocess.PIPE
575 p = subprocess.Popen(args, cwd=cwd, stdout=stdout, stderr=stderr) 597 p = subprocess.Popen(args, cwd=cwd, stdout=stdout, stderr=stderr, preexec_fn=preexec_fn)
598 _currentSubprocess = (p, args)
576 if callable(out): 599 if callable(out):
577 t = Thread(target=redirect, args=(p.stdout, out)) 600 t = Thread(target=redirect, args=(p.stdout, out))
578 t.daemon = True # thread dies with the program 601 t.daemon = True # thread dies with the program
579 t.start() 602 t.start()
580 if callable(err): 603 if callable(err):
590 except OSError as e: 613 except OSError as e:
591 log('Error executing \'' + ' '.join(args) + '\': ' + str(e)) 614 log('Error executing \'' + ' '.join(args) + '\': ' + str(e))
592 if _opts.verbose: 615 if _opts.verbose:
593 raise e 616 raise e
594 abort(e.errno) 617 abort(e.errno)
618 finally:
619 _currentSubprocess = None
595 620
596 621
597 if retcode and nonZeroIsFatal: 622 if retcode and nonZeroIsFatal:
598 if _opts.verbose: 623 if _opts.verbose:
599 raise subprocess.CalledProcessError(retcode, ' '.join(args)) 624 raise subprocess.CalledProcessError(retcode, ' '.join(args))
1183 'projects': [show_projects, ''], 1208 'projects': [show_projects, ''],
1184 } 1209 }
1185 1210
1186 _argParser = ArgParser() 1211 _argParser = ArgParser()
1187 1212
1188 def main(): 1213 def main():
1189 MX_INCLUDES = os.environ.get('MX_INCLUDES', None) 1214 MX_INCLUDES = os.environ.get('MX_INCLUDES', None)
1190 if MX_INCLUDES is not None: 1215 if MX_INCLUDES is not None:
1191 for path in MX_INCLUDES.split(os.pathsep): 1216 for path in MX_INCLUDES.split(os.pathsep):
1192 d = join(path, 'mx') 1217 d = join(path, 'mx')
1193 if exists(d) and isdir(d): 1218 if exists(d) and isdir(d):
1218 1243
1219 c, _ = commands[command][:2] 1244 c, _ = commands[command][:2]
1220 try: 1245 try:
1221 if opts.timeout != 0: 1246 if opts.timeout != 0:
1222 def alarm_handler(signum, frame): 1247 def alarm_handler(signum, frame):
1248 #import traceback
1249 #traceback.print_stack()
1250 currentSubprocess = _currentSubprocess
1251 if currentSubprocess is not None:
1252 p, args = currentSubprocess
1253 log('Killing subprocess due to command timeout: ' + ' '.join(args))
1254 _kill_process_group(p.pid)
1223 abort('Command timed out after ' + str(opts.timeout) + ' seconds: ' + ' '.join(commandAndArgs)) 1255 abort('Command timed out after ' + str(opts.timeout) + ' seconds: ' + ' '.join(commandAndArgs))
1224 signal.signal(signal.SIGALRM, alarm_handler) 1256 signal.signal(signal.SIGALRM, alarm_handler)
1225 signal.alarm(opts.timeout) 1257 signal.alarm(opts.timeout)
1226 retcode = c(command_args) 1258 retcode = c(command_args)
1227 if retcode is not None and retcode != 0: 1259 if retcode is not None and retcode != 0: