comparison mxtool/mx.py @ 15489:d0e3f6963ed7

mx: made parallel Java builds interact correctly with management of subprocesses upon abort/quit
author Doug Simon <doug.simon@oracle.com>
date Sun, 04 May 2014 01:26:50 +0200
parents 05d3f069cff2
children 5d0dd6a6f6b3
comparison
equal deleted inserted replaced
15488:d370d87e528f 15489:d0e3f6963ed7
1298 abort('Process timed out after {0} seconds: {1}'.format(timeout, ' '.join(args))) 1298 abort('Process timed out after {0} seconds: {1}'.format(timeout, ' '.join(args)))
1299 delay = min(delay * 2, remaining, .05) 1299 delay = min(delay * 2, remaining, .05)
1300 time.sleep(delay) 1300 time.sleep(delay)
1301 1301
1302 # Makes the current subprocess accessible to the abort() function 1302 # Makes the current subprocess accessible to the abort() function
1303 # This is a tuple of the Popen object and args. 1303 # This is a list of tuples of the subprocess.Popen or
1304 _currentSubprocess = (None, None) 1304 # multiprocessing.Process object and args.
1305 _currentSubprocesses = []
1306
1307 def _addSubprocess(p, args):
1308 entry = (p, args)
1309 _currentSubprocesses.append(entry)
1310 return entry
1311
1312 def _removeSubprocess(entry):
1313 if entry and entry in _currentSubprocesses:
1314 try:
1315 _currentSubprocesses.remove(entry)
1316 except:
1317 pass
1305 1318
1306 def waitOn(p): 1319 def waitOn(p):
1307 if get_os() == 'windows': 1320 if get_os() == 'windows':
1308 # on windows use a poll loop, otherwise signal does not get handled 1321 # on windows use a poll loop, otherwise signal does not get handled
1309 retcode = None 1322 retcode = None
1338 log(' '.join(map(pipes.quote, args))) 1351 log(' '.join(map(pipes.quote, args)))
1339 1352
1340 if timeout is None and _opts.ptimeout != 0: 1353 if timeout is None and _opts.ptimeout != 0:
1341 timeout = _opts.ptimeout 1354 timeout = _opts.ptimeout
1342 1355
1343 global _currentSubprocess 1356 sub = None
1344
1345 try: 1357 try:
1346 # On Unix, the new subprocess should be in a separate group so that a timeout alarm 1358 # On Unix, the new subprocess should be in a separate group so that a timeout alarm
1347 # can use os.killpg() to kill the whole subprocess group 1359 # can use os.killpg() to kill the whole subprocess group
1348 preexec_fn = None 1360 preexec_fn = None
1349 creationflags = 0 1361 creationflags = 0
1357 f(line) 1369 f(line)
1358 stream.close() 1370 stream.close()
1359 stdout = out if not callable(out) else subprocess.PIPE 1371 stdout = out if not callable(out) else subprocess.PIPE
1360 stderr = err if not callable(err) else subprocess.PIPE 1372 stderr = err if not callable(err) else subprocess.PIPE
1361 p = subprocess.Popen(args, cwd=cwd, stdout=stdout, stderr=stderr, preexec_fn=preexec_fn, creationflags=creationflags, env=env) 1373 p = subprocess.Popen(args, cwd=cwd, stdout=stdout, stderr=stderr, preexec_fn=preexec_fn, creationflags=creationflags, env=env)
1362 _currentSubprocess = (p, args) 1374 sub = _addSubprocess(p, args)
1363 if callable(out): 1375 if callable(out):
1364 t = Thread(target=redirect, args=(p.stdout, out)) 1376 t = Thread(target=redirect, args=(p.stdout, out))
1365 t.daemon = True # thread dies with the program 1377 t.daemon = True # thread dies with the program
1366 t.start() 1378 t.start()
1367 if callable(err): 1379 if callable(err):
1380 raise e 1392 raise e
1381 abort(e.errno) 1393 abort(e.errno)
1382 except KeyboardInterrupt: 1394 except KeyboardInterrupt:
1383 abort(1) 1395 abort(1)
1384 finally: 1396 finally:
1385 _currentSubprocess = (None, None) 1397 _removeSubprocess(sub)
1386 1398
1387 if retcode and nonZeroIsFatal: 1399 if retcode and nonZeroIsFatal:
1388 if _opts.verbose: 1400 if _opts.verbose:
1389 if _opts.very_verbose: 1401 if _opts.very_verbose:
1390 raise subprocess.CalledProcessError(retcode, ' '.join(args)) 1402 raise subprocess.CalledProcessError(retcode, ' '.join(args))
1661 if '$' in result or '%' in result: 1673 if '$' in result or '%' in result:
1662 abort('Property contains an undefined environment variable: ' + value) 1674 abort('Property contains an undefined environment variable: ' + value)
1663 return result 1675 return result
1664 1676
1665 def _send_sigquit(): 1677 def _send_sigquit():
1666 p, args = _currentSubprocess 1678 for p, args in _currentSubprocesses:
1667 1679
1668 def _isJava(): 1680 def _isJava():
1669 if args: 1681 if args:
1670 name = args[0].split(os.sep)[-1] 1682 name = args[0].split(os.sep)[-1]
1671 return name == "java" 1683 return name == "java"
1672 return False 1684 return False
1673 1685
1674 if p is not None and _isJava(): 1686 if p is not None and _isJava():
1675 if get_os() == 'windows': 1687 if get_os() == 'windows':
1676 log("mx: implement me! want to send SIGQUIT to my child process") 1688 log("mx: implement me! want to send SIGQUIT to my child process")
1677 else: 1689 else:
1678 _kill_process_group(p.pid, sig=signal.SIGQUIT) 1690 _kill_process_group(p.pid, sig=signal.SIGQUIT)
1679 time.sleep(0.1) 1691 time.sleep(0.1)
1680
1681 1692
1682 def abort(codeOrMessage): 1693 def abort(codeOrMessage):
1683 """ 1694 """
1684 Aborts the program with a SystemExit exception. 1695 Aborts the program with a SystemExit exception.
1685 If 'codeOrMessage' is a plain integer, it specifies the system exit status; 1696 If 'codeOrMessage' is a plain integer, it specifies the system exit status;
1690 if _opts.killwithsigquit: 1701 if _opts.killwithsigquit:
1691 _send_sigquit() 1702 _send_sigquit()
1692 1703
1693 # import traceback 1704 # import traceback
1694 # traceback.print_stack() 1705 # traceback.print_stack()
1695 p, _ = _currentSubprocess 1706 for p, args in _currentSubprocesses:
1696 if p is not None: 1707 try:
1697 if get_os() == 'windows': 1708 if get_os() == 'windows':
1698 p.kill() 1709 p.terminate()
1699 else: 1710 else:
1700 _kill_process_group(p.pid, signal.SIGKILL) 1711 _kill_process_group(p.pid, signal.SIGKILL)
1712 except BaseException as e:
1713 log('error while killing subprocess {} "{}": {}'.format(p.pid, ' '.join(args), e))
1701 1714
1702 raise SystemExit(codeOrMessage) 1715 raise SystemExit(codeOrMessage)
1703 1716
1704 def download(path, urls, verbose=False): 1717 def download(path, urls, verbose=False):
1705 """ 1718 """
2111 active = [] 2124 active = []
2112 for t in tasks: 2125 for t in tasks:
2113 if t.proc.is_alive(): 2126 if t.proc.is_alive():
2114 active.append(t) 2127 active.append(t)
2115 else: 2128 else:
2129 _removeSubprocess(t.sub)
2116 if t.proc.exitcode != 0: 2130 if t.proc.exitcode != 0:
2117 return ([], joinTasks(tasks)) 2131 return ([], joinTasks(tasks))
2118 return (active, []) 2132 return (active, [])
2119 2133
2120 def remainingDepsDepth(task): 2134 def remainingDepsDepth(task):
2160 if depsDone(task): 2174 if depsDone(task):
2161 worklist.remove(task) 2175 worklist.remove(task)
2162 task.proc = multiprocessing.Process(target=executeTask, args=(task,)) 2176 task.proc = multiprocessing.Process(target=executeTask, args=(task,))
2163 task.proc.start() 2177 task.proc.start()
2164 active.append(task) 2178 active.append(task)
2179 task.sub = _addSubprocess(task.proc, ['JavaCompileTask', str(task)])
2165 if len(active) == cpus: 2180 if len(active) == cpus:
2166 break 2181 break
2167 2182
2168 worklist = sortWorklist(worklist) 2183 worklist = sortWorklist(worklist)
2169 joinTasks(active) 2184 joinTasks(active)