#!/usr/bin/env python
import sys, os, cPickle, time, mmap
TICKS = ('|', '/', '\\')
ESC = chr(27)
class Busy(object):
def __init__(self, pid):
self.tick = -1
sys.stdout.write('Running Child(pid:%d)...%s[s' % (pid, ESC))
self.next()
def next(self):
self.tick += 1
sys.stdout.write('%s[K %s%s[u' % (ESC, TICKS[self.tick%3], ESC))
sys.stdout.flush()
def stop(self, status):
sys.stdout.write('Done(status: %d)\n' % status)
class ForkedProcessException(Exception):
pass
def run_in_separate_process(waitclass, func, *args, **kwds):
try:
mmsize = kwds['mmsize']
del kwds['mmsize']
mmsize = max(mmsize, 1024)
except KeyError:
mmsize = 1024
mm = mmap.mmap(-1, mmsize)
pid = os.fork()
if pid != 0:
# the parent process
busy = waitclass(pid)
try:
while 1:
busy.next()
wpid, wstatus = os.waitpid(pid, os.WNOHANG)
if wpid == pid:
break
except KeyboardInterrupt:
raise ForkedProcessException('User cancelled!')
if os.WIFEXITED(wstatus):
status = os.WEXITSTATUS(wstatus)
busy.stop(status)
elif os.WIFSIGNALED(wstatus):
raise ForkedProcessException('Child killed by signal: %d' % os.WTERMSIG(wstatus))
else:
raise RuntimeError('Unknown child exit status!')
mm.seek(0)
result = cPickle.load(mm)
if status == 0:
return result
else:
raise result
else: # the child process
try:
mm.seek(0)
result = func(*args, **kwds)
status = 0 # success
cPickle.dump(result, mm, cPickle.HIGHEST_PROTOCOL)
except cPickle.PicklingError, exc:
status = 2 # failure
cPickle.dump(exc, mm, cPickle.HIGHEST_PROTOCOL)
except (KeyboardInterrupt), exc:
status = 4 # failure
cPickle.dump(ForkedProcessException('User cancelled!'), mm, cPickle.HIGHEST_PROTOCOL)
except ValueError:
status = 3 # failure
pstr = cPickle.dumps(result, cPickle.HIGHEST_PROTOCOL)
mm.seek(0)
cPickle.dump(ForkedProcessException('mmsize: %d, need: %d' % (mmsize, len(pstr))), mm, cPickle.HIGHEST_PROTOCOL)
except (Exception), exc:
status = 1 # failure
cPickle.dump(exc, mm, cPickle.HIGHEST_PROTOCOL)
os._exit(status)
# Functions to run in a separate process
def treble(x, fail=False):
if fail: 1/0
return 3 * x
def suicide():
os.kill(os.getpid(), 15)
def toobig():
return '1234567890' * 110
def nocanpickle():
return globals()
def waitaround(seconds=3, fail=False):
while seconds:
if fail: 1/0
time.sleep(1)
seconds -= 1
return ['here', 'is', 'the', 'dead', 'tree', 'devoid', 'of', 'leaves']
def sysexit():
sys.exit(9)
# General test function call
def run(direct, func, *args, **kwargs):
try:
print '\nRunning %s(%s, %s) ' % (func.func_name, args, kwargs),
if direct:
print 'directly...'
result = func(*args, **kwargs)
print 'Needs minimum mmsize of %d' % (len(cPickle.dumps(result, cPickle.HIGHEST_PROTOCOL)))
else:
print 'in separate process...'
result = run_in_separate_process(Busy, func, *args, **kwargs)
print '%s returned: %s' % (func.func_name, result)
except Exception, e:
print '%s raised %s: %s' % (func.func_name, e.__class__.__name__, str(e))
def main():
direct = True
run(not direct, waitaround, seconds=30)
run(not direct, waitaround)
run(not direct, waitaround, fail=True)
run(not direct, toobig)
run(not direct, nocanpickle)
run(not direct, suicide)
run(direct, waitaround, seconds=5)
run(not direct, sysexit)
run(not direct, treble, 4)
run(direct, treble, 4)
if __name__ == '__main__':
main()