/Lib/multiprocessing/process.py

http://unladen-swallow.googlecode.com/ · Python · 297 lines · 180 code · 43 blank · 74 comment · 40 complexity · f52a6e19667503d1d4d3bdc7ccb87262 MD5 · raw file

  1. #
  2. # Module providing the `Process` class which emulates `threading.Thread`
  3. #
  4. # multiprocessing/process.py
  5. #
  6. # Copyright (c) 2006-2008, R Oudkerk --- see COPYING.txt
  7. #
  8. __all__ = ['Process', 'current_process', 'active_children']
  9. #
  10. # Imports
  11. #
  12. import os
  13. import sys
  14. import signal
  15. import itertools
  16. #
  17. #
  18. #
  19. try:
  20. ORIGINAL_DIR = os.path.abspath(os.getcwd())
  21. except OSError:
  22. ORIGINAL_DIR = None
  23. #
  24. # Public functions
  25. #
  26. def current_process():
  27. '''
  28. Return process object representing the current process
  29. '''
  30. return _current_process
  31. def active_children():
  32. '''
  33. Return list of process objects corresponding to live child processes
  34. '''
  35. _cleanup()
  36. return list(_current_process._children)
  37. #
  38. #
  39. #
  40. def _cleanup():
  41. # check for processes which have finished
  42. for p in list(_current_process._children):
  43. if p._popen.poll() is not None:
  44. _current_process._children.discard(p)
  45. #
  46. # The `Process` class
  47. #
  48. class Process(object):
  49. '''
  50. Process objects represent activity that is run in a separate process
  51. The class is analagous to `threading.Thread`
  52. '''
  53. _Popen = None
  54. def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
  55. assert group is None, 'group argument must be None for now'
  56. count = _current_process._counter.next()
  57. self._identity = _current_process._identity + (count,)
  58. self._authkey = _current_process._authkey
  59. self._daemonic = _current_process._daemonic
  60. self._tempdir = _current_process._tempdir
  61. self._parent_pid = os.getpid()
  62. self._popen = None
  63. self._target = target
  64. self._args = tuple(args)
  65. self._kwargs = dict(kwargs)
  66. self._name = name or type(self).__name__ + '-' + \
  67. ':'.join(str(i) for i in self._identity)
  68. def run(self):
  69. '''
  70. Method to be run in sub-process; can be overridden in sub-class
  71. '''
  72. if self._target:
  73. self._target(*self._args, **self._kwargs)
  74. def start(self):
  75. '''
  76. Start child process
  77. '''
  78. assert self._popen is None, 'cannot start a process twice'
  79. assert self._parent_pid == os.getpid(), \
  80. 'can only start a process object created by current process'
  81. assert not _current_process._daemonic, \
  82. 'daemonic processes are not allowed to have children'
  83. _cleanup()
  84. if self._Popen is not None:
  85. Popen = self._Popen
  86. else:
  87. from .forking import Popen
  88. self._popen = Popen(self)
  89. _current_process._children.add(self)
  90. def terminate(self):
  91. '''
  92. Terminate process; sends SIGTERM signal or uses TerminateProcess()
  93. '''
  94. self._popen.terminate()
  95. def join(self, timeout=None):
  96. '''
  97. Wait until child process terminates
  98. '''
  99. assert self._parent_pid == os.getpid(), 'can only join a child process'
  100. assert self._popen is not None, 'can only join a started process'
  101. res = self._popen.wait(timeout)
  102. if res is not None:
  103. _current_process._children.discard(self)
  104. def is_alive(self):
  105. '''
  106. Return whether process is alive
  107. '''
  108. if self is _current_process:
  109. return True
  110. assert self._parent_pid == os.getpid(), 'can only test a child process'
  111. if self._popen is None:
  112. return False
  113. self._popen.poll()
  114. return self._popen.returncode is None
  115. @property
  116. def name(self):
  117. return self._name
  118. @name.setter
  119. def name(self, name):
  120. assert isinstance(name, str), 'name must be a string'
  121. self._name = name
  122. @property
  123. def daemon(self):
  124. '''
  125. Return whether process is a daemon
  126. '''
  127. return self._daemonic
  128. @daemon.setter
  129. def daemon(self, daemonic):
  130. '''
  131. Set whether process is a daemon
  132. '''
  133. assert self._popen is None, 'process has already started'
  134. self._daemonic = daemonic
  135. @property
  136. def authkey(self):
  137. return self._authkey
  138. @authkey.setter
  139. def authkey(self, authkey):
  140. '''
  141. Set authorization key of process
  142. '''
  143. self._authkey = AuthenticationString(authkey)
  144. @property
  145. def exitcode(self):
  146. '''
  147. Return exit code of process or `None` if it has yet to stop
  148. '''
  149. if self._popen is None:
  150. return self._popen
  151. return self._popen.poll()
  152. @property
  153. def ident(self):
  154. '''
  155. Return indentifier (PID) of process or `None` if it has yet to start
  156. '''
  157. if self is _current_process:
  158. return os.getpid()
  159. else:
  160. return self._popen and self._popen.pid
  161. pid = ident
  162. def __repr__(self):
  163. if self is _current_process:
  164. status = 'started'
  165. elif self._parent_pid != os.getpid():
  166. status = 'unknown'
  167. elif self._popen is None:
  168. status = 'initial'
  169. else:
  170. if self._popen.poll() is not None:
  171. status = self.exitcode
  172. else:
  173. status = 'started'
  174. if type(status) is int:
  175. if status == 0:
  176. status = 'stopped'
  177. else:
  178. status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
  179. return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
  180. status, self._daemonic and ' daemon' or '')
  181. ##
  182. def _bootstrap(self):
  183. from . import util
  184. global _current_process
  185. try:
  186. self._children = set()
  187. self._counter = itertools.count(1)
  188. try:
  189. sys.stdin.close()
  190. sys.stdin = open(os.devnull)
  191. except (OSError, ValueError):
  192. pass
  193. _current_process = self
  194. util._finalizer_registry.clear()
  195. util._run_after_forkers()
  196. util.info('child process calling self.run()')
  197. try:
  198. self.run()
  199. exitcode = 0
  200. finally:
  201. util._exit_function()
  202. except SystemExit, e:
  203. if not e.args:
  204. exitcode = 1
  205. elif type(e.args[0]) is int:
  206. exitcode = e.args[0]
  207. else:
  208. sys.stderr.write(e.args[0] + '\n')
  209. sys.stderr.flush()
  210. exitcode = 1
  211. except:
  212. exitcode = 1
  213. import traceback
  214. sys.stderr.write('Process %s:\n' % self.name)
  215. sys.stderr.flush()
  216. traceback.print_exc()
  217. util.info('process exiting with exitcode %d' % exitcode)
  218. return exitcode
  219. #
  220. # We subclass bytes to avoid accidental transmission of auth keys over network
  221. #
  222. class AuthenticationString(bytes):
  223. def __reduce__(self):
  224. from .forking import Popen
  225. if not Popen.thread_is_spawning():
  226. raise TypeError(
  227. 'Pickling an AuthenticationString object is '
  228. 'disallowed for security reasons'
  229. )
  230. return AuthenticationString, (bytes(self),)
  231. #
  232. # Create object representing the main process
  233. #
  234. class _MainProcess(Process):
  235. def __init__(self):
  236. self._identity = ()
  237. self._daemonic = False
  238. self._name = 'MainProcess'
  239. self._parent_pid = None
  240. self._popen = None
  241. self._counter = itertools.count(1)
  242. self._children = set()
  243. self._authkey = AuthenticationString(os.urandom(32))
  244. self._tempdir = None
  245. _current_process = _MainProcess()
  246. del _MainProcess
  247. #
  248. # Give names to some return codes
  249. #
  250. _exitcode_to_name = {}
  251. for name, signum in signal.__dict__.items():
  252. if name[:3]=='SIG' and '_' not in name:
  253. _exitcode_to_name[-signum] = name