blob: 8dc4b005fc44e3a6dd0a5120ce428606709890f4 [file] [log] [blame]
Benjamin Petersone711caf2008-06-11 16:44:04 +00001#
2# Module for starting a process object using os.fork() or CreateProcess()
3#
4# multiprocessing/forking.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
35import os
36import sys
37import signal
Richard Oudkerk7aaa1ef2013-02-26 12:39:57 +000038import errno
Benjamin Petersone711caf2008-06-11 16:44:04 +000039
40from multiprocessing import util, process
41
Amaury Forgeot d'Arc949d47d2008-08-19 21:30:55 +000042__all__ = ['Popen', 'assert_spawning', 'exit', 'duplicate', 'close', 'ForkingPickler']
Benjamin Petersone711caf2008-06-11 16:44:04 +000043
44#
45# Check that the current thread is spawning a child process
46#
47
48def assert_spawning(self):
49 if not Popen.thread_is_spawning():
50 raise RuntimeError(
51 '%s objects should only be shared between processes'
52 ' through inheritance' % type(self).__name__
53 )
54
55#
Amaury Forgeot d'Arc949d47d2008-08-19 21:30:55 +000056# Try making some callable types picklable
57#
58
59from pickle import _Pickler as Pickler
60class ForkingPickler(Pickler):
61 dispatch = Pickler.dispatch.copy()
62 @classmethod
63 def register(cls, type, reduce):
64 def dispatcher(self, obj):
65 rv = reduce(obj)
66 if isinstance(rv, str):
67 self.save_global(obj, rv)
68 else:
69 self.save_reduce(obj=obj, *rv)
70 cls.dispatch[type] = dispatcher
71
72def _reduce_method(m):
73 if m.__self__ is None:
74 return getattr, (m.__class__, m.__func__.__name__)
75 else:
76 return getattr, (m.__self__, m.__func__.__name__)
77class _C:
78 def f(self):
79 pass
80ForkingPickler.register(type(_C().f), _reduce_method)
81
82
83def _reduce_method_descriptor(m):
84 return getattr, (m.__objclass__, m.__name__)
85ForkingPickler.register(type(list.append), _reduce_method_descriptor)
86ForkingPickler.register(type(int.__add__), _reduce_method_descriptor)
87
88try:
89 from functools import partial
90except ImportError:
91 pass
92else:
93 def _reduce_partial(p):
94 return _rebuild_partial, (p.func, p.args, p.keywords or {})
95 def _rebuild_partial(func, args, keywords):
96 return partial(func, *args, **keywords)
97 ForkingPickler.register(partial, _reduce_partial)
98
99#
Benjamin Petersone711caf2008-06-11 16:44:04 +0000100# Unix
101#
102
103if sys.platform != 'win32':
104 import time
105
106 exit = os._exit
107 duplicate = os.dup
108 close = os.close
109
110 #
111 # We define a Popen class similar to the one from subprocess, but
112 # whose constructor takes a process object as its argument.
113 #
114
115 class Popen(object):
116
117 def __init__(self, process_obj):
118 sys.stdout.flush()
119 sys.stderr.flush()
120 self.returncode = None
121
122 self.pid = os.fork()
123 if self.pid == 0:
124 if 'random' in sys.modules:
125 import random
126 random.seed()
127 code = process_obj._bootstrap()
Benjamin Petersone711caf2008-06-11 16:44:04 +0000128 os._exit(code)
129
130 def poll(self, flag=os.WNOHANG):
131 if self.returncode is None:
Richard Oudkerk7aaa1ef2013-02-26 12:39:57 +0000132 while True:
133 try:
134 pid, sts = os.waitpid(self.pid, flag)
135 except os.error as e:
136 if e.errno == errno.EINTR:
137 continue
138 # Child process not yet created. See #1731717
139 # e.errno == errno.ECHILD == 10
140 return None
141 else:
142 break
Benjamin Petersone711caf2008-06-11 16:44:04 +0000143 if pid == self.pid:
144 if os.WIFSIGNALED(sts):
145 self.returncode = -os.WTERMSIG(sts)
146 else:
147 assert os.WIFEXITED(sts)
148 self.returncode = os.WEXITSTATUS(sts)
149 return self.returncode
150
151 def wait(self, timeout=None):
152 if timeout is None:
153 return self.poll(0)
154 deadline = time.time() + timeout
155 delay = 0.0005
156 while 1:
157 res = self.poll()
158 if res is not None:
159 break
160 remaining = deadline - time.time()
161 if remaining <= 0:
162 break
163 delay = min(delay * 2, remaining, 0.05)
164 time.sleep(delay)
165 return res
166
167 def terminate(self):
168 if self.returncode is None:
169 try:
170 os.kill(self.pid, signal.SIGTERM)
171 except OSError as e:
172 if self.wait(timeout=0.1) is None:
173 raise
174
175 @staticmethod
176 def thread_is_spawning():
177 return False
178
179#
180# Windows
181#
182
183else:
184 import _thread
185 import msvcrt
186 import _subprocess
Benjamin Petersone711caf2008-06-11 16:44:04 +0000187 import time
188
Jesse Nollerf70a5382009-01-18 19:44:02 +0000189 from pickle import dump, load, HIGHEST_PROTOCOL
Brian Curtina6a32742010-08-04 15:47:24 +0000190 from _multiprocessing import win32, Connection, PipeConnection
Benjamin Petersone711caf2008-06-11 16:44:04 +0000191 from .util import Finalize
192
Amaury Forgeot d'Arc949d47d2008-08-19 21:30:55 +0000193 def dump(obj, file, protocol=None):
194 ForkingPickler(file, protocol).dump(obj)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000195
196 #
197 #
198 #
199
200 TERMINATE = 0x10000
201 WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
brian.curtine2f29982011-04-11 17:56:23 -0500202 WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
Benjamin Petersone711caf2008-06-11 16:44:04 +0000203
204 exit = win32.ExitProcess
205 close = win32.CloseHandle
206
207 #
208 # _python_exe is the assumed path to the python executable.
209 # People embedding Python want to modify it.
210 #
211
brian.curtine2f29982011-04-11 17:56:23 -0500212 if WINSERVICE:
Benjamin Petersone711caf2008-06-11 16:44:04 +0000213 _python_exe = os.path.join(sys.exec_prefix, 'python.exe')
214 else:
215 _python_exe = sys.executable
216
217 def set_executable(exe):
218 global _python_exe
219 _python_exe = exe
220
221 #
222 #
223 #
224
225 def duplicate(handle, target_process=None, inheritable=False):
226 if target_process is None:
227 target_process = _subprocess.GetCurrentProcess()
228 return _subprocess.DuplicateHandle(
229 _subprocess.GetCurrentProcess(), handle, target_process,
230 0, inheritable, _subprocess.DUPLICATE_SAME_ACCESS
231 ).Detach()
232
233 #
234 # We define a Popen class similar to the one from subprocess, but
235 # whose constructor takes a process object as its argument.
236 #
237
238 class Popen(object):
239 '''
240 Start a subprocess to run the code of a process object
241 '''
242 _tls = _thread._local()
243
244 def __init__(self, process_obj):
245 # create pipe for communication with child
246 rfd, wfd = os.pipe()
247
248 # get handle for read end of the pipe and make it inheritable
249 rhandle = duplicate(msvcrt.get_osfhandle(rfd), inheritable=True)
250 os.close(rfd)
251
252 # start process
253 cmd = get_command_line() + [rhandle]
254 cmd = ' '.join('"%s"' % x for x in cmd)
255 hp, ht, pid, tid = _subprocess.CreateProcess(
256 _python_exe, cmd, None, None, 1, 0, None, None, None
257 )
258 ht.Close()
259 close(rhandle)
260
261 # set attributes of self
262 self.pid = pid
263 self.returncode = None
264 self._handle = hp
265
266 # send information to child
267 prep_data = get_preparation_data(process_obj._name)
268 to_child = os.fdopen(wfd, 'wb')
269 Popen._tls.process_handle = int(hp)
270 try:
271 dump(prep_data, to_child, HIGHEST_PROTOCOL)
272 dump(process_obj, to_child, HIGHEST_PROTOCOL)
273 finally:
274 del Popen._tls.process_handle
275 to_child.close()
276
277 @staticmethod
278 def thread_is_spawning():
279 return getattr(Popen._tls, 'process_handle', None) is not None
280
281 @staticmethod
282 def duplicate_for_child(handle):
283 return duplicate(handle, Popen._tls.process_handle)
284
285 def wait(self, timeout=None):
286 if self.returncode is None:
287 if timeout is None:
288 msecs = _subprocess.INFINITE
289 else:
290 msecs = max(0, int(timeout * 1000 + 0.5))
291
292 res = _subprocess.WaitForSingleObject(int(self._handle), msecs)
293 if res == _subprocess.WAIT_OBJECT_0:
294 code = _subprocess.GetExitCodeProcess(self._handle)
295 if code == TERMINATE:
296 code = -signal.SIGTERM
297 self.returncode = code
298
299 return self.returncode
300
301 def poll(self):
302 return self.wait(timeout=0)
303
304 def terminate(self):
305 if self.returncode is None:
306 try:
307 _subprocess.TerminateProcess(int(self._handle), TERMINATE)
308 except WindowsError:
309 if self.wait(timeout=0.1) is None:
310 raise
311
312 #
313 #
314 #
315
316 def is_forking(argv):
317 '''
318 Return whether commandline indicates we are forking
319 '''
320 if len(argv) >= 2 and argv[1] == '--multiprocessing-fork':
321 assert len(argv) == 3
322 return True
323 else:
324 return False
325
326
327 def freeze_support():
328 '''
329 Run code for process object if this in not the main process
330 '''
331 if is_forking(sys.argv):
332 main()
333 sys.exit()
334
335
336 def get_command_line():
337 '''
338 Returns prefix of command line used for spawning a child process
339 '''
Richard Oudkerke88a2442012-08-14 11:41:32 +0100340 if getattr(process.current_process(), '_inheriting', False):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000341 raise RuntimeError('''
342 Attempt to start a new process before the current process
343 has finished its bootstrapping phase.
344
345 This probably means that you are on Windows and you have
346 forgotten to use the proper idiom in the main module:
347
348 if __name__ == '__main__':
349 freeze_support()
350 ...
351
352 The "freeze_support()" line can be omitted if the program
353 is not going to be frozen to produce a Windows executable.''')
354
355 if getattr(sys, 'frozen', False):
356 return [sys.executable, '--multiprocessing-fork']
357 else:
358 prog = 'from multiprocessing.forking import main; main()'
359 return [_python_exe, '-c', prog, '--multiprocessing-fork']
360
361
362 def main():
363 '''
364 Run code specifed by data received over pipe
365 '''
366 assert is_forking(sys.argv)
367
368 handle = int(sys.argv[-1])
369 fd = msvcrt.open_osfhandle(handle, os.O_RDONLY)
370 from_parent = os.fdopen(fd, 'rb')
371
372 process.current_process()._inheriting = True
373 preparation_data = load(from_parent)
374 prepare(preparation_data)
375 self = load(from_parent)
376 process.current_process()._inheriting = False
377
378 from_parent.close()
379
380 exitcode = self._bootstrap()
381 exit(exitcode)
382
383
384 def get_preparation_data(name):
385 '''
386 Return info about parent needed by child to unpickle process object
387 '''
388 from .util import _logger, _log_to_stderr
389
390 d = dict(
391 name=name,
392 sys_path=sys.path,
393 sys_argv=sys.argv,
394 log_to_stderr=_log_to_stderr,
395 orig_dir=process.ORIGINAL_DIR,
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000396 authkey=process.current_process().authkey,
Benjamin Petersone711caf2008-06-11 16:44:04 +0000397 )
398
399 if _logger is not None:
400 d['log_level'] = _logger.getEffectiveLevel()
401
brian.curtine2f29982011-04-11 17:56:23 -0500402 if not WINEXE and not WINSERVICE:
Benjamin Petersone711caf2008-06-11 16:44:04 +0000403 main_path = getattr(sys.modules['__main__'], '__file__', None)
404 if not main_path and sys.argv[0] not in ('', '-c'):
405 main_path = sys.argv[0]
406 if main_path is not None:
407 if not os.path.isabs(main_path) and \
408 process.ORIGINAL_DIR is not None:
409 main_path = os.path.join(process.ORIGINAL_DIR, main_path)
410 d['main_path'] = os.path.normpath(main_path)
411
412 return d
413
414 #
415 # Make (Pipe)Connection picklable
416 #
417
418 def reduce_connection(conn):
419 if not Popen.thread_is_spawning():
420 raise RuntimeError(
421 'By default %s objects can only be shared between processes\n'
422 'using inheritance' % type(conn).__name__
423 )
424 return type(conn), (Popen.duplicate_for_child(conn.fileno()),
425 conn.readable, conn.writable)
426
Amaury Forgeot d'Arc949d47d2008-08-19 21:30:55 +0000427 ForkingPickler.register(Connection, reduce_connection)
428 ForkingPickler.register(PipeConnection, reduce_connection)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000429
430#
431# Prepare current process
432#
433
434old_main_modules = []
435
436def prepare(data):
437 '''
438 Try to get current process ready to unpickle process object
439 '''
440 old_main_modules.append(sys.modules['__main__'])
441
442 if 'name' in data:
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000443 process.current_process().name = data['name']
Benjamin Petersone711caf2008-06-11 16:44:04 +0000444
445 if 'authkey' in data:
446 process.current_process()._authkey = data['authkey']
447
448 if 'log_to_stderr' in data and data['log_to_stderr']:
449 util.log_to_stderr()
450
451 if 'log_level' in data:
452 util.get_logger().setLevel(data['log_level'])
453
454 if 'sys_path' in data:
455 sys.path = data['sys_path']
456
457 if 'sys_argv' in data:
458 sys.argv = data['sys_argv']
459
460 if 'dir' in data:
461 os.chdir(data['dir'])
462
463 if 'orig_dir' in data:
464 process.ORIGINAL_DIR = data['orig_dir']
465
466 if 'main_path' in data:
Nick Coghlan793ee1f2011-01-30 01:24:08 +0000467 # XXX (ncoghlan): The following code makes several bogus
468 # assumptions regarding the relationship between __file__
469 # and a module's real name. See PEP 302 and issue #10845
Benjamin Petersone711caf2008-06-11 16:44:04 +0000470 main_path = data['main_path']
471 main_name = os.path.splitext(os.path.basename(main_path))[0]
472 if main_name == '__init__':
473 main_name = os.path.basename(os.path.dirname(main_path))
474
Nick Coghlan793ee1f2011-01-30 01:24:08 +0000475 if main_name == '__main__':
476 main_module = sys.modules['__main__']
477 main_module.__file__ = main_path
478 elif main_name != 'ipython':
479 # Main modules not actually called __main__.py may
480 # contain additional code that should still be executed
Benjamin Petersone711caf2008-06-11 16:44:04 +0000481 import imp
482
483 if main_path is None:
484 dirs = None
485 elif os.path.basename(main_path).startswith('__init__.py'):
486 dirs = [os.path.dirname(os.path.dirname(main_path))]
487 else:
488 dirs = [os.path.dirname(main_path)]
489
490 assert main_name not in sys.modules, main_name
491 file, path_name, etc = imp.find_module(main_name, dirs)
492 try:
493 # We would like to do "imp.load_module('__main__', ...)"
494 # here. However, that would cause 'if __name__ ==
495 # "__main__"' clauses to be executed.
496 main_module = imp.load_module(
497 '__parents_main__', file, path_name, etc
498 )
499 finally:
500 if file:
501 file.close()
502
503 sys.modules['__main__'] = main_module
504 main_module.__name__ = '__main__'
505
506 # Try to make the potentially picklable objects in
507 # sys.modules['__main__'] realize they are in the main
508 # module -- somewhat ugly.
509 for obj in list(main_module.__dict__.values()):
510 try:
511 if obj.__module__ == '__parents_main__':
512 obj.__module__ = '__main__'
513 except Exception:
514 pass