forkedfunc.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. """
  2. ForkedFunc provides a way to run a function in a forked process
  3. and get at its return value, stdout and stderr output as well
  4. as signals and exitstatusus.
  5. """
  6. import py
  7. import os
  8. import sys
  9. import marshal
  10. def get_unbuffered_io(fd, filename):
  11. f = open(str(filename), "w")
  12. if fd != f.fileno():
  13. os.dup2(f.fileno(), fd)
  14. class AutoFlush:
  15. def write(self, data):
  16. f.write(data)
  17. f.flush()
  18. def __getattr__(self, name):
  19. return getattr(f, name)
  20. return AutoFlush()
  21. class ForkedFunc:
  22. EXITSTATUS_EXCEPTION = 3
  23. def __init__(self, fun, args=None, kwargs=None, nice_level=0,
  24. child_on_start=None, child_on_exit=None):
  25. if args is None:
  26. args = []
  27. if kwargs is None:
  28. kwargs = {}
  29. self.fun = fun
  30. self.args = args
  31. self.kwargs = kwargs
  32. self.tempdir = tempdir = py.path.local.mkdtemp()
  33. self.RETVAL = tempdir.ensure('retval')
  34. self.STDOUT = tempdir.ensure('stdout')
  35. self.STDERR = tempdir.ensure('stderr')
  36. pid = os.fork()
  37. if pid: # in parent process
  38. self.pid = pid
  39. else: # in child process
  40. self.pid = None
  41. self._child(nice_level, child_on_start, child_on_exit)
  42. def _child(self, nice_level, child_on_start, child_on_exit):
  43. # right now we need to call a function, but first we need to
  44. # map all IO that might happen
  45. sys.stdout = stdout = get_unbuffered_io(1, self.STDOUT)
  46. sys.stderr = stderr = get_unbuffered_io(2, self.STDERR)
  47. retvalf = self.RETVAL.open("wb")
  48. EXITSTATUS = 0
  49. try:
  50. if nice_level:
  51. os.nice(nice_level)
  52. try:
  53. if child_on_start is not None:
  54. child_on_start()
  55. retval = self.fun(*self.args, **self.kwargs)
  56. retvalf.write(marshal.dumps(retval))
  57. if child_on_exit is not None:
  58. child_on_exit()
  59. except:
  60. excinfo = py.code.ExceptionInfo()
  61. stderr.write(str(excinfo._getreprcrash()))
  62. EXITSTATUS = self.EXITSTATUS_EXCEPTION
  63. finally:
  64. stdout.close()
  65. stderr.close()
  66. retvalf.close()
  67. os.close(1)
  68. os.close(2)
  69. os._exit(EXITSTATUS)
  70. def waitfinish(self, waiter=os.waitpid):
  71. pid, systemstatus = waiter(self.pid, 0)
  72. if systemstatus:
  73. if os.WIFSIGNALED(systemstatus):
  74. exitstatus = os.WTERMSIG(systemstatus) + 128
  75. else:
  76. exitstatus = os.WEXITSTATUS(systemstatus)
  77. else:
  78. exitstatus = 0
  79. signal = systemstatus & 0x7f
  80. if not exitstatus and not signal:
  81. retval = self.RETVAL.open('rb')
  82. try:
  83. retval_data = retval.read()
  84. finally:
  85. retval.close()
  86. retval = marshal.loads(retval_data)
  87. else:
  88. retval = None
  89. stdout = self.STDOUT.read()
  90. stderr = self.STDERR.read()
  91. self._removetemp()
  92. return Result(exitstatus, signal, retval, stdout, stderr)
  93. def _removetemp(self):
  94. if self.tempdir.check():
  95. self.tempdir.remove()
  96. def __del__(self):
  97. if self.pid is not None: # only clean up in main process
  98. self._removetemp()
  99. class Result(object):
  100. def __init__(self, exitstatus, signal, retval, stdout, stderr):
  101. self.exitstatus = exitstatus
  102. self.signal = signal
  103. self.retval = retval
  104. self.out = stdout
  105. self.err = stderr