multiprocess updates:

- avoid sending keyboard interrupt signals to child processes
  - parallelizer keeps better track of processes that die unexpectedly
  - added ability to specify a different executable when starting new processes
This commit is contained in:
Luke Campagnola 2012-08-17 16:15:13 -04:00
parent 03d618e1b8
commit e21480855f
4 changed files with 30 additions and 8 deletions

View File

@ -1,7 +1,8 @@
"""For starting up remote processes"""
import sys, pickle
import sys, pickle, os
if __name__ == '__main__':
os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process
name, port, authkey, targetStr, path = pickle.load(sys.stdin)
if path is not None:
## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list.

View File

@ -85,12 +85,14 @@ class Parallelize:
def __exit__(self, *exc_info):
if self.proc is not None: ## worker
exceptOccurred = exc_info[0] is not None ## hit an exception during processing.
try:
if exc_info[0] is not None:
if exceptOccurred:
sys.excepthook(*exc_info)
finally:
#print os.getpid(), 'exit'
os._exit(0)
os._exit(1 if exceptOccurred else 0)
else: ## parent
if self.showProgress:
@ -137,6 +139,7 @@ class Parallelize:
## process events from workers until all have exited.
activeChilds = self.childs[:]
self.exitCodes = []
pollInterval = 0.01
while len(activeChilds) > 0:
waitingChildren = 0
@ -156,7 +159,8 @@ class Parallelize:
activeChilds.remove(ch)
while True:
try:
os.waitpid(ch.childPid, 0)
pid, exitcode = os.waitpid(ch.childPid, 0)
self.exitCodes.append(exitcode)
break
except OSError as ex:
if ex.errno == 4: ## If we get this error, just try again
@ -183,6 +187,11 @@ class Parallelize:
finally:
if self.showProgress:
self.progressDlg.__exit__(None, None, None)
if len(self.exitCodes) < len(self.childs):
raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes)))
for code in self.exitCodes:
if code != 0:
raise Exception("Error occurred in parallel-executed subprocess (console output may have more information).")
return [] ## no tasks for parent process.

View File

@ -31,7 +31,7 @@ class Process(RemoteEventHandler):
ProxyObject for more information.
"""
def __init__(self, name=None, target=None, copySysPath=True):
def __init__(self, name=None, target=None, executable=None, copySysPath=True):
"""
============ =============================================================
Arguments:
@ -50,6 +50,8 @@ class Process(RemoteEventHandler):
target = startEventLoop
if name is None:
name = str(self)
if executable is None:
executable = sys.executable
## random authentication key
authkey = ''.join([chr(random.getrandbits(7)) for i in range(20)])
@ -68,7 +70,7 @@ class Process(RemoteEventHandler):
## start remote process, instruct it to run target function
sysPath = sys.path if copySysPath else None
bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py'))
self.proc = subprocess.Popen((sys.executable, bootstrap), stdin=subprocess.PIPE)
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE)
targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to
## set its sys.path properly before unpickling the target
pickle.dump((name+'_child', port, authkey, targetStr, sysPath), self.proc.stdin)
@ -166,7 +168,8 @@ class ForkedProcess(RemoteEventHandler):
## - no reading/writing file handles/sockets owned by parent process (stdout is ok)
## - don't touch QtGui or QApplication at all; these are landmines.
## - don't let the process call exit handlers
## -
os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process
## close all file handles we do not want shared with parent
conn.close()

View File

@ -60,7 +60,11 @@ class RemoteEventHandler(object):
@classmethod
def getHandler(cls, pid):
try:
return cls.handlers[pid]
except:
print pid, cls.handlers
raise
def getProxyOption(self, opt):
return self.proxyOptions[opt]
@ -88,6 +92,11 @@ class RemoteEventHandler(object):
except ExitError:
self.exited = True
raise
except IOError as err:
if err.errno == 4: ## interrupted system call; try again
continue
else:
raise
except:
print "Error in process %s" % self.name
sys.excepthook(*sys.exc_info())