blob: 99ee5326c38fc145bb567f5b05c3f80d0d42b576 [file] [log] [blame]
Benjamin Petersone711caf2008-06-11 16:44:04 +00001#
2# Module providing the `Process` class which emulates `threading.Thread`
3#
4# multiprocessing/process.py
5#
R. David Murray3fc969a2010-12-14 01:38:16 +00006# Copyright (c) 2006-2008, R Oudkerk
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12#
13# 1. Redistributions of source code must retain the above copyright
14# notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16# notice, this list of conditions and the following disclaimer in the
17# documentation and/or other materials provided with the distribution.
18# 3. Neither the name of author nor the names of any contributors may be
19# used to endorse or promote products derived from this software
20# without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
23# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32# SUCH DAMAGE.
Benjamin Petersone711caf2008-06-11 16:44:04 +000033#
34
35__all__ = ['Process', 'current_process', 'active_children']
36
37#
38# Imports
39#
40
41import os
42import sys
43import signal
44import itertools
45
46#
47#
48#
49
50try:
51 ORIGINAL_DIR = os.path.abspath(os.getcwd())
52except OSError:
53 ORIGINAL_DIR = None
54
Benjamin Petersone711caf2008-06-11 16:44:04 +000055#
56# Public functions
57#
58
59def current_process():
60 '''
61 Return process object representing the current process
62 '''
63 return _current_process
64
65def active_children():
66 '''
67 Return list of process objects corresponding to live child processes
68 '''
69 _cleanup()
70 return list(_current_process._children)
71
72#
73#
74#
75
76def _cleanup():
77 # check for processes which have finished
78 for p in list(_current_process._children):
79 if p._popen.poll() is not None:
80 _current_process._children.discard(p)
81
82#
83# The `Process` class
84#
85
86class Process(object):
87 '''
88 Process objects represent activity that is run in a separate process
89
90 The class is analagous to `threading.Thread`
91 '''
92 _Popen = None
93
Antoine Pitrou0bd4deb2011-02-25 22:07:43 +000094 def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
95 *, daemon=None):
Benjamin Petersone711caf2008-06-11 16:44:04 +000096 assert group is None, 'group argument must be None for now'
97 count = next(_current_process._counter)
98 self._identity = _current_process._identity + (count,)
99 self._authkey = _current_process._authkey
Antoine Pitrou0bd4deb2011-02-25 22:07:43 +0000100 if daemon is not None:
101 self._daemonic = daemon
102 else:
103 self._daemonic = _current_process._daemonic
Benjamin Petersone711caf2008-06-11 16:44:04 +0000104 self._tempdir = _current_process._tempdir
105 self._parent_pid = os.getpid()
106 self._popen = None
107 self._target = target
108 self._args = tuple(args)
109 self._kwargs = dict(kwargs)
110 self._name = name or type(self).__name__ + '-' + \
111 ':'.join(str(i) for i in self._identity)
112
113 def run(self):
114 '''
115 Method to be run in sub-process; can be overridden in sub-class
116 '''
117 if self._target:
118 self._target(*self._args, **self._kwargs)
119
120 def start(self):
121 '''
122 Start child process
123 '''
124 assert self._popen is None, 'cannot start a process twice'
125 assert self._parent_pid == os.getpid(), \
126 'can only start a process object created by current process'
127 assert not _current_process._daemonic, \
128 'daemonic processes are not allowed to have children'
129 _cleanup()
130 if self._Popen is not None:
131 Popen = self._Popen
132 else:
133 from .forking import Popen
134 self._popen = Popen(self)
Antoine Pitrou176f07d2011-06-06 19:35:31 +0200135 self._sentinel = self._popen.sentinel
Benjamin Petersone711caf2008-06-11 16:44:04 +0000136 _current_process._children.add(self)
137
138 def terminate(self):
139 '''
140 Terminate process; sends SIGTERM signal or uses TerminateProcess()
141 '''
142 self._popen.terminate()
143
144 def join(self, timeout=None):
145 '''
146 Wait until child process terminates
147 '''
148 assert self._parent_pid == os.getpid(), 'can only join a child process'
149 assert self._popen is not None, 'can only join a started process'
150 res = self._popen.wait(timeout)
151 if res is not None:
152 _current_process._children.discard(self)
153
154 def is_alive(self):
155 '''
156 Return whether process is alive
157 '''
158 if self is _current_process:
159 return True
160 assert self._parent_pid == os.getpid(), 'can only test a child process'
161 if self._popen is None:
162 return False
163 self._popen.poll()
164 return self._popen.returncode is None
165
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000166 @property
167 def name(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000168 return self._name
169
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000170 @name.setter
171 def name(self, name):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000172 assert isinstance(name, str), 'name must be a string'
173 self._name = name
174
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000175 @property
176 def daemon(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000177 '''
178 Return whether process is a daemon
179 '''
180 return self._daemonic
181
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000182 @daemon.setter
183 def daemon(self, daemonic):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000184 '''
185 Set whether process is a daemon
186 '''
187 assert self._popen is None, 'process has already started'
188 self._daemonic = daemonic
189
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000190 @property
191 def authkey(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000192 return self._authkey
193
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000194 @authkey.setter
195 def authkey(self, authkey):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000196 '''
197 Set authorization key of process
198 '''
199 self._authkey = AuthenticationString(authkey)
200
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000201 @property
202 def exitcode(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000203 '''
204 Return exit code of process or `None` if it has yet to stop
205 '''
206 if self._popen is None:
207 return self._popen
208 return self._popen.poll()
209
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000210 @property
211 def ident(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000212 '''
Florent Xiclunab519d232010-03-04 16:10:55 +0000213 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Petersone711caf2008-06-11 16:44:04 +0000214 '''
215 if self is _current_process:
216 return os.getpid()
217 else:
218 return self._popen and self._popen.pid
219
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000220 pid = ident
Benjamin Petersone711caf2008-06-11 16:44:04 +0000221
Antoine Pitrou176f07d2011-06-06 19:35:31 +0200222 @property
223 def sentinel(self):
224 '''
225 Return a file descriptor (Unix) or handle (Windows) suitable for
226 waiting for process termination.
227 '''
228 try:
229 return self._sentinel
230 except AttributeError:
231 raise ValueError("process not started")
232
Benjamin Petersone711caf2008-06-11 16:44:04 +0000233 def __repr__(self):
234 if self is _current_process:
235 status = 'started'
236 elif self._parent_pid != os.getpid():
237 status = 'unknown'
238 elif self._popen is None:
239 status = 'initial'
240 else:
241 if self._popen.poll() is not None:
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000242 status = self.exitcode
Benjamin Petersone711caf2008-06-11 16:44:04 +0000243 else:
244 status = 'started'
245
246 if type(status) is int:
247 if status == 0:
248 status = 'stopped'
249 else:
250 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
251
252 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
253 status, self._daemonic and ' daemon' or '')
254
255 ##
256
257 def _bootstrap(self):
258 from . import util
259 global _current_process
260
261 try:
262 self._children = set()
263 self._counter = itertools.count(1)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000264 if sys.stdin is not None:
265 try:
Alexandre Vassalottic57a84f2009-07-17 12:07:01 +0000266 sys.stdin.close()
267 sys.stdin = open(os.devnull)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000268 except (OSError, ValueError):
269 pass
Benjamin Petersone711caf2008-06-11 16:44:04 +0000270 _current_process = self
271 util._finalizer_registry.clear()
272 util._run_after_forkers()
273 util.info('child process calling self.run()')
274 try:
275 self.run()
276 exitcode = 0
277 finally:
278 util._exit_function()
279 except SystemExit as e:
280 if not e.args:
281 exitcode = 1
282 elif type(e.args[0]) is int:
283 exitcode = e.args[0]
284 else:
285 sys.stderr.write(e.args[0] + '\n')
286 sys.stderr.flush()
287 exitcode = 1
288 except:
289 exitcode = 1
290 import traceback
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000291 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000292 sys.stderr.flush()
293 traceback.print_exc()
294
295 util.info('process exiting with exitcode %d' % exitcode)
296 return exitcode
297
298#
299# We subclass bytes to avoid accidental transmission of auth keys over network
300#
301
302class AuthenticationString(bytes):
303 def __reduce__(self):
304 from .forking import Popen
305 if not Popen.thread_is_spawning():
306 raise TypeError(
307 'Pickling an AuthenticationString object is '
308 'disallowed for security reasons'
309 )
310 return AuthenticationString, (bytes(self),)
311
312#
313# Create object representing the main process
314#
315
316class _MainProcess(Process):
317
318 def __init__(self):
319 self._identity = ()
320 self._daemonic = False
321 self._name = 'MainProcess'
322 self._parent_pid = None
323 self._popen = None
324 self._counter = itertools.count(1)
325 self._children = set()
326 self._authkey = AuthenticationString(os.urandom(32))
327 self._tempdir = None
328
329_current_process = _MainProcess()
330del _MainProcess
331
332#
333# Give names to some return codes
334#
335
336_exitcode_to_name = {}
337
338for name, signum in list(signal.__dict__.items()):
339 if name[:3]=='SIG' and '_' not in name:
340 _exitcode_to_name[-signum] = name