blob: 16c4e1eb3437367b4e387925ff6c62b5f0001660 [file] [log] [blame]
Benjamin Peterson7f03ea72008-06-13 19:20:48 +00001#
2# Module providing the `Process` class which emulates `threading.Thread`
3#
4# multiprocessing/process.py
5#
R. David Murray79af2452010-12-14 01:42:40 +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 Peterson7f03ea72008-06-13 19:20:48 +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 Peterson7f03ea72008-06-13 19:20:48 +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
94 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
95 assert group is None, 'group argument must be None for now'
96 count = _current_process._counter.next()
97 self._identity = _current_process._identity + (count,)
98 self._authkey = _current_process._authkey
99 self._daemonic = _current_process._daemonic
100 self._tempdir = _current_process._tempdir
101 self._parent_pid = os.getpid()
102 self._popen = None
103 self._target = target
104 self._args = tuple(args)
105 self._kwargs = dict(kwargs)
106 self._name = name or type(self).__name__ + '-' + \
107 ':'.join(str(i) for i in self._identity)
108
109 def run(self):
110 '''
111 Method to be run in sub-process; can be overridden in sub-class
112 '''
113 if self._target:
114 self._target(*self._args, **self._kwargs)
115
116 def start(self):
117 '''
118 Start child process
119 '''
120 assert self._popen is None, 'cannot start a process twice'
121 assert self._parent_pid == os.getpid(), \
122 'can only start a process object created by current process'
123 assert not _current_process._daemonic, \
124 'daemonic processes are not allowed to have children'
125 _cleanup()
126 if self._Popen is not None:
127 Popen = self._Popen
128 else:
129 from .forking import Popen
130 self._popen = Popen(self)
Antoine Pitrou12536bd2017-06-28 13:48:38 +0200131 # Avoid a refcycle if the target function holds an indirect
132 # reference to the process object (see bpo-30775)
133 del self._target, self._args, self._kwargs
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000134 _current_process._children.add(self)
135
136 def terminate(self):
137 '''
138 Terminate process; sends SIGTERM signal or uses TerminateProcess()
139 '''
140 self._popen.terminate()
141
142 def join(self, timeout=None):
143 '''
144 Wait until child process terminates
145 '''
146 assert self._parent_pid == os.getpid(), 'can only join a child process'
147 assert self._popen is not None, 'can only join a started process'
148 res = self._popen.wait(timeout)
149 if res is not None:
150 _current_process._children.discard(self)
151
152 def is_alive(self):
153 '''
154 Return whether process is alive
155 '''
156 if self is _current_process:
157 return True
158 assert self._parent_pid == os.getpid(), 'can only test a child process'
159 if self._popen is None:
160 return False
161 self._popen.poll()
162 return self._popen.returncode is None
163
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000164 @property
165 def name(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000166 return self._name
167
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000168 @name.setter
169 def name(self, name):
Benjamin Peterson27cae342009-12-24 15:19:40 +0000170 assert isinstance(name, basestring), 'name must be a string'
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000171 self._name = name
172
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000173 @property
174 def daemon(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000175 '''
176 Return whether process is a daemon
177 '''
178 return self._daemonic
179
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000180 @daemon.setter
181 def daemon(self, daemonic):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000182 '''
183 Set whether process is a daemon
184 '''
185 assert self._popen is None, 'process has already started'
186 self._daemonic = daemonic
187
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000188 @property
189 def authkey(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000190 return self._authkey
191
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000192 @authkey.setter
193 def authkey(self, authkey):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000194 '''
195 Set authorization key of process
196 '''
197 self._authkey = AuthenticationString(authkey)
198
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000199 @property
200 def exitcode(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000201 '''
202 Return exit code of process or `None` if it has yet to stop
203 '''
204 if self._popen is None:
205 return self._popen
206 return self._popen.poll()
207
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000208 @property
209 def ident(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000210 '''
Florent Xicluna3bc5cb72010-03-04 15:58:54 +0000211 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000212 '''
213 if self is _current_process:
214 return os.getpid()
215 else:
216 return self._popen and self._popen.pid
217
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000218 pid = ident
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000219
220 def __repr__(self):
221 if self is _current_process:
222 status = 'started'
223 elif self._parent_pid != os.getpid():
224 status = 'unknown'
225 elif self._popen is None:
226 status = 'initial'
227 else:
228 if self._popen.poll() is not None:
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000229 status = self.exitcode
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000230 else:
231 status = 'started'
232
Serhiy Storchaka994f04d2016-12-27 15:09:36 +0200233 if type(status) in (int, long):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000234 if status == 0:
235 status = 'stopped'
236 else:
237 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
238
239 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
240 status, self._daemonic and ' daemon' or '')
241
242 ##
243
244 def _bootstrap(self):
245 from . import util
246 global _current_process
247
248 try:
249 self._children = set()
250 self._counter = itertools.count(1)
251 try:
Jesse Noller1b90efb2009-06-30 17:11:52 +0000252 sys.stdin.close()
253 sys.stdin = open(os.devnull)
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000254 except (OSError, ValueError):
255 pass
256 _current_process = self
257 util._finalizer_registry.clear()
258 util._run_after_forkers()
259 util.info('child process calling self.run()')
260 try:
261 self.run()
262 exitcode = 0
263 finally:
264 util._exit_function()
265 except SystemExit, e:
266 if not e.args:
267 exitcode = 1
Serhiy Storchaka994f04d2016-12-27 15:09:36 +0200268 elif isinstance(e.args[0], (int, long)):
269 exitcode = int(e.args[0])
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000270 else:
Richard Oudkerk2182e052012-06-06 19:01:14 +0100271 sys.stderr.write(str(e.args[0]) + '\n')
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000272 sys.stderr.flush()
Richard Oudkerk3f8376e2013-11-17 17:24:11 +0000273 exitcode = 1
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000274 except:
275 exitcode = 1
276 import traceback
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000277 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000278 sys.stderr.flush()
279 traceback.print_exc()
280
281 util.info('process exiting with exitcode %d' % exitcode)
282 return exitcode
283
284#
285# We subclass bytes to avoid accidental transmission of auth keys over network
286#
287
288class AuthenticationString(bytes):
289 def __reduce__(self):
290 from .forking import Popen
291 if not Popen.thread_is_spawning():
292 raise TypeError(
293 'Pickling an AuthenticationString object is '
294 'disallowed for security reasons'
295 )
296 return AuthenticationString, (bytes(self),)
297
298#
299# Create object representing the main process
300#
301
302class _MainProcess(Process):
303
304 def __init__(self):
305 self._identity = ()
306 self._daemonic = False
307 self._name = 'MainProcess'
308 self._parent_pid = None
309 self._popen = None
310 self._counter = itertools.count(1)
311 self._children = set()
312 self._authkey = AuthenticationString(os.urandom(32))
313 self._tempdir = None
314
315_current_process = _MainProcess()
316del _MainProcess
317
318#
319# Give names to some return codes
320#
321
322_exitcode_to_name = {}
323
324for name, signum in signal.__dict__.items():
325 if name[:3]=='SIG' and '_' not in name:
326 _exitcode_to_name[-signum] = name