Mercurial > hg > truffle
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: |