blob: 3fb9ff600fa33f8dbc40b36a8842d86efa8e0f97 [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)
135 _current_process._children.add(self)
136
137 def terminate(self):
138 '''
139 Terminate process; sends SIGTERM signal or uses TerminateProcess()
140 '''
141 self._popen.terminate()
142
143 def join(self, timeout=None):
144 '''
145 Wait until child process terminates
146 '''
147 assert self._parent_pid == os.getpid(), 'can only join a child process'
148 assert self._popen is not None, 'can only join a started process'
149 res = self._popen.wait(timeout)
150 if res is not None:
151 _current_process._children.discard(self)
152
153 def is_alive(self):
154 '''
155 Return whether process is alive
156 '''
157 if self is _current_process:
158 return True
159 assert self._parent_pid == os.getpid(), 'can only test a child process'
160 if self._popen is None:
161 return False
162 self._popen.poll()
163 return self._popen.returncode is None
164
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000165 @property
166 def name(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000167 return self._name
168
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000169 @name.setter
170 def name(self, name):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000171 assert isinstance(name, str), 'name must be a string'
172 self._name = name
173
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000174 @property
175 def daemon(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000176 '''
177 Return whether process is a daemon
178 '''
179 return self._daemonic
180
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000181 @daemon.setter
182 def daemon(self, daemonic):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000183 '''
184 Set whether process is a daemon
185 '''
186 assert self._popen is None, 'process has already started'
187 self._daemonic = daemonic
188
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000189 @property
190 def authkey(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000191 return self._authkey
192
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000193 @authkey.setter
194 def authkey(self, authkey):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000195 '''
196 Set authorization key of process
197 '''
198 self._authkey = AuthenticationString(authkey)
199
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000200 @property
201 def exitcode(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000202 '''
203 Return exit code of process or `None` if it has yet to stop
204 '''
205 if self._popen is None:
206 return self._popen
207 return self._popen.poll()
208
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000209 @property
210 def ident(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000211 '''
Florent Xiclunab519d232010-03-04 16:10:55 +0000212 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Petersone711caf2008-06-11 16:44:04 +0000213 '''
214 if self is _current_process:
215 return os.getpid()
216 else:
217 return self._popen and self._popen.pid
218
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000219 pid = ident
Benjamin Petersone711caf2008-06-11 16:44:04 +0000220
221 def __repr__(self):
222 if self is _current_process:
223 status = 'started'
224 elif self._parent_pid != os.getpid():
225 status = 'unknown'
226 elif self._popen is None:
227 status = 'initial'
228 else:
229 if self._popen.poll() is not None:
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000230 status = self.exitcode
Benjamin Petersone711caf2008-06-11 16:44:04 +0000231 else:
232 status = 'started'
233
234 if type(status) is int:
235 if status == 0:
236 status = 'stopped'
237 else:
238 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
239
240 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
241 status, self._daemonic and ' daemon' or '')
242
243 ##
244
245 def _bootstrap(self):
246 from . import util
247 global _current_process
248
249 try:
250 self._children = set()
251 self._counter = itertools.count(1)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000252 if sys.stdin is not None:
253 try:
Alexandre Vassalottic57a84f2009-07-17 12:07:01 +0000254 sys.stdin.close()
255 sys.stdin = open(os.devnull)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000256 except (OSError, ValueError):
257 pass
Benjamin Petersone711caf2008-06-11 16:44:04 +0000258 _current_process = self
259 util._finalizer_registry.clear()
260 util._run_after_forkers()
261 util.info('child process calling self.run()')
262 try:
263 self.run()
264 exitcode = 0
265 finally:
266 util._exit_function()
267 except SystemExit as e:
268 if not e.args:
269 exitcode = 1
270 elif type(e.args[0]) is int:
271 exitcode = e.args[0]
272 else:
273 sys.stderr.write(e.args[0] + '\n')
274 sys.stderr.flush()
275 exitcode = 1
276 except:
277 exitcode = 1
278 import traceback
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000279 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000280 sys.stderr.flush()
281 traceback.print_exc()
282
283 util.info('process exiting with exitcode %d' % exitcode)
284 return exitcode
285
286#
287# We subclass bytes to avoid accidental transmission of auth keys over network
288#
289
290class AuthenticationString(bytes):
291 def __reduce__(self):
292 from .forking import Popen
293 if not Popen.thread_is_spawning():
294 raise TypeError(
295 'Pickling an AuthenticationString object is '
296 'disallowed for security reasons'
297 )
298 return AuthenticationString, (bytes(self),)
299
300#
301# Create object representing the main process
302#
303
304class _MainProcess(Process):
305
306 def __init__(self):
307 self._identity = ()
308 self._daemonic = False
309 self._name = 'MainProcess'
310 self._parent_pid = None
311 self._popen = None
312 self._counter = itertools.count(1)
313 self._children = set()
314 self._authkey = AuthenticationString(os.urandom(32))
315 self._tempdir = None
316
317_current_process = _MainProcess()
318del _MainProcess
319
320#
321# Give names to some return codes
322#
323
324_exitcode_to_name = {}
325
326for name, signum in list(signal.__dict__.items()):
327 if name[:3]=='SIG' and '_' not in name:
328 _exitcode_to_name[-signum] = name