blob: 11c8fca360fa22edc255dbf16bf1b38f948b3e47 [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'
Victor Stinnerb65cb8a2017-07-26 17:54:42 +0200159
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000160 if self._popen is None:
161 return False
Victor Stinnerb65cb8a2017-07-26 17:54:42 +0200162
163 returncode = self._popen.poll()
164 if returncode is None:
165 return True
166 else:
167 _current_process._children.discard(self)
168 return False
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000169
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000170 @property
171 def name(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000172 return self._name
173
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000174 @name.setter
175 def name(self, name):
Benjamin Peterson27cae342009-12-24 15:19:40 +0000176 assert isinstance(name, basestring), 'name must be a string'
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000177 self._name = name
178
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000179 @property
180 def daemon(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000181 '''
182 Return whether process is a daemon
183 '''
184 return self._daemonic
185
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000186 @daemon.setter
187 def daemon(self, daemonic):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000188 '''
189 Set whether process is a daemon
190 '''
191 assert self._popen is None, 'process has already started'
192 self._daemonic = daemonic
193
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000194 @property
195 def authkey(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000196 return self._authkey
197
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000198 @authkey.setter
199 def authkey(self, authkey):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000200 '''
201 Set authorization key of process
202 '''
203 self._authkey = AuthenticationString(authkey)
204
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000205 @property
206 def exitcode(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000207 '''
208 Return exit code of process or `None` if it has yet to stop
209 '''
210 if self._popen is None:
211 return self._popen
212 return self._popen.poll()
213
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000214 @property
215 def ident(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000216 '''
Florent Xicluna3bc5cb72010-03-04 15:58:54 +0000217 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000218 '''
219 if self is _current_process:
220 return os.getpid()
221 else:
222 return self._popen and self._popen.pid
223
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000224 pid = ident
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000225
226 def __repr__(self):
227 if self is _current_process:
228 status = 'started'
229 elif self._parent_pid != os.getpid():
230 status = 'unknown'
231 elif self._popen is None:
232 status = 'initial'
233 else:
234 if self._popen.poll() is not None:
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000235 status = self.exitcode
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000236 else:
237 status = 'started'
238
Serhiy Storchaka994f04d2016-12-27 15:09:36 +0200239 if type(status) in (int, long):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000240 if status == 0:
241 status = 'stopped'
242 else:
243 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
244
245 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
246 status, self._daemonic and ' daemon' or '')
247
248 ##
249
250 def _bootstrap(self):
251 from . import util
252 global _current_process
253
254 try:
255 self._children = set()
256 self._counter = itertools.count(1)
257 try:
Jesse Noller1b90efb2009-06-30 17:11:52 +0000258 sys.stdin.close()
259 sys.stdin = open(os.devnull)
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000260 except (OSError, ValueError):
261 pass
262 _current_process = self
263 util._finalizer_registry.clear()
264 util._run_after_forkers()
265 util.info('child process calling self.run()')
266 try:
267 self.run()
268 exitcode = 0
269 finally:
270 util._exit_function()
271 except SystemExit, e:
272 if not e.args:
273 exitcode = 1
Serhiy Storchaka994f04d2016-12-27 15:09:36 +0200274 elif isinstance(e.args[0], (int, long)):
275 exitcode = int(e.args[0])
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000276 else:
Richard Oudkerk2182e052012-06-06 19:01:14 +0100277 sys.stderr.write(str(e.args[0]) + '\n')
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000278 sys.stderr.flush()
Richard Oudkerk3f8376e2013-11-17 17:24:11 +0000279 exitcode = 1
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000280 except:
281 exitcode = 1
282 import traceback
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000283 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000284 sys.stderr.flush()
285 traceback.print_exc()
286
287 util.info('process exiting with exitcode %d' % exitcode)
288 return exitcode
289
290#
291# We subclass bytes to avoid accidental transmission of auth keys over network
292#
293
294class AuthenticationString(bytes):
295 def __reduce__(self):
296 from .forking import Popen
297 if not Popen.thread_is_spawning():
298 raise TypeError(
299 'Pickling an AuthenticationString object is '
300 'disallowed for security reasons'
301 )
302 return AuthenticationString, (bytes(self),)
303
304#
305# Create object representing the main process
306#
307
308class _MainProcess(Process):
309
310 def __init__(self):
311 self._identity = ()
312 self._daemonic = False
313 self._name = 'MainProcess'
314 self._parent_pid = None
315 self._popen = None
316 self._counter = itertools.count(1)
317 self._children = set()
318 self._authkey = AuthenticationString(os.urandom(32))
319 self._tempdir = None
320
321_current_process = _MainProcess()
322del _MainProcess
323
324#
325# Give names to some return codes
326#
327
328_exitcode_to_name = {}
329
330for name, signum in signal.__dict__.items():
331 if name[:3]=='SIG' and '_' not in name:
332 _exitcode_to_name[-signum] = name