Spaces:
Running
Running
# | |
# Code used to start processes when using the spawn or forkserver | |
# start methods. | |
# | |
# multiprocessing/spawn.py | |
# | |
# Copyright (c) 2006-2008, R Oudkerk | |
# Licensed to PSF under a Contributor Agreement. | |
# | |
import os | |
import sys | |
import runpy | |
import types | |
from . import get_start_method, set_start_method | |
from . import process | |
from .context import reduction | |
from . import util | |
__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable', | |
'get_preparation_data', 'get_command_line', 'import_main_path'] | |
# | |
# _python_exe is the assumed path to the python executable. | |
# People embedding Python want to modify it. | |
# | |
if sys.platform != 'win32': | |
WINEXE = False | |
WINSERVICE = False | |
else: | |
WINEXE = getattr(sys, 'frozen', False) | |
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe") | |
if WINSERVICE: | |
_python_exe = os.path.join(sys.exec_prefix, 'python.exe') | |
else: | |
_python_exe = sys.executable | |
def set_executable(exe): | |
global _python_exe | |
_python_exe = exe | |
def get_executable(): | |
return _python_exe | |
# | |
# | |
# | |
def is_forking(argv): | |
''' | |
Return whether commandline indicates we are forking | |
''' | |
if len(argv) >= 2 and argv[1] == '--multiprocessing-fork': | |
return True | |
else: | |
return False | |
def freeze_support(): | |
''' | |
Run code for process object if this in not the main process | |
''' | |
if is_forking(sys.argv): | |
kwds = {} | |
for arg in sys.argv[2:]: | |
name, value = arg.split('=') | |
if value == 'None': | |
kwds[name] = None | |
else: | |
kwds[name] = int(value) | |
spawn_main(**kwds) | |
sys.exit() | |
def get_command_line(**kwds): | |
''' | |
Returns prefix of command line used for spawning a child process | |
''' | |
if getattr(sys, 'frozen', False): | |
return ([sys.executable, '--multiprocessing-fork'] + | |
['%s=%r' % item for item in kwds.items()]) | |
else: | |
prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)' | |
prog %= ', '.join('%s=%r' % item for item in kwds.items()) | |
opts = util._args_from_interpreter_flags() | |
return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork'] | |
def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): | |
''' | |
Run code specified by data received over pipe | |
''' | |
assert is_forking(sys.argv), "Not forking" | |
if sys.platform == 'win32': | |
import msvcrt | |
import _winapi | |
if parent_pid is not None: | |
source_process = _winapi.OpenProcess( | |
_winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE, | |
False, parent_pid) | |
else: | |
source_process = None | |
new_handle = reduction.duplicate(pipe_handle, | |
source_process=source_process) | |
fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY) | |
parent_sentinel = source_process | |
else: | |
from . import resource_tracker | |
resource_tracker._resource_tracker._fd = tracker_fd | |
fd = pipe_handle | |
parent_sentinel = os.dup(pipe_handle) | |
exitcode = _main(fd, parent_sentinel) | |
sys.exit(exitcode) | |
def _main(fd, parent_sentinel): | |
with os.fdopen(fd, 'rb', closefd=True) as from_parent: | |
process.current_process()._inheriting = True | |
try: | |
preparation_data = reduction.pickle.load(from_parent) | |
prepare(preparation_data) | |
self = reduction.pickle.load(from_parent) | |
finally: | |
del process.current_process()._inheriting | |
return self._bootstrap(parent_sentinel) | |
def _check_not_importing_main(): | |
if getattr(process.current_process(), '_inheriting', False): | |
raise RuntimeError(''' | |
An attempt has been made to start a new process before the | |
current process has finished its bootstrapping phase. | |
This probably means that you are not using fork to start your | |
child processes and you have forgotten to use the proper idiom | |
in the main module: | |
if __name__ == '__main__': | |
freeze_support() | |
... | |
The "freeze_support()" line can be omitted if the program | |
is not going to be frozen to produce an executable.''') | |
def get_preparation_data(name): | |
''' | |
Return info about parent needed by child to unpickle process object | |
''' | |
_check_not_importing_main() | |
d = dict( | |
log_to_stderr=util._log_to_stderr, | |
authkey=process.current_process().authkey, | |
) | |
if util._logger is not None: | |
d['log_level'] = util._logger.getEffectiveLevel() | |
sys_path=sys.path.copy() | |
try: | |
i = sys_path.index('') | |
except ValueError: | |
pass | |
else: | |
sys_path[i] = process.ORIGINAL_DIR | |
d.update( | |
name=name, | |
sys_path=sys_path, | |
sys_argv=sys.argv, | |
orig_dir=process.ORIGINAL_DIR, | |
dir=os.getcwd(), | |
start_method=get_start_method(), | |
) | |
# Figure out whether to initialise main in the subprocess as a module | |
# or through direct execution (or to leave it alone entirely) | |
main_module = sys.modules['__main__'] | |
main_mod_name = getattr(main_module.__spec__, "name", None) | |
if main_mod_name is not None: | |
d['init_main_from_name'] = main_mod_name | |
elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE): | |
main_path = getattr(main_module, '__file__', None) | |
if main_path is not None: | |
if (not os.path.isabs(main_path) and | |
process.ORIGINAL_DIR is not None): | |
main_path = os.path.join(process.ORIGINAL_DIR, main_path) | |
d['init_main_from_path'] = os.path.normpath(main_path) | |
return d | |
# | |
# Prepare current process | |
# | |
old_main_modules = [] | |
def prepare(data): | |
''' | |
Try to get current process ready to unpickle process object | |
''' | |
if 'name' in data: | |
process.current_process().name = data['name'] | |
if 'authkey' in data: | |
process.current_process().authkey = data['authkey'] | |
if 'log_to_stderr' in data and data['log_to_stderr']: | |
util.log_to_stderr() | |
if 'log_level' in data: | |
util.get_logger().setLevel(data['log_level']) | |
if 'sys_path' in data: | |
sys.path = data['sys_path'] | |
if 'sys_argv' in data: | |
sys.argv = data['sys_argv'] | |
if 'dir' in data: | |
os.chdir(data['dir']) | |
if 'orig_dir' in data: | |
process.ORIGINAL_DIR = data['orig_dir'] | |
if 'start_method' in data: | |
set_start_method(data['start_method'], force=True) | |
if 'init_main_from_name' in data: | |
_fixup_main_from_name(data['init_main_from_name']) | |
elif 'init_main_from_path' in data: | |
_fixup_main_from_path(data['init_main_from_path']) | |
# Multiprocessing module helpers to fix up the main module in | |
# spawned subprocesses | |
def _fixup_main_from_name(mod_name): | |
# __main__.py files for packages, directories, zip archives, etc, run | |
# their "main only" code unconditionally, so we don't even try to | |
# populate anything in __main__, nor do we make any changes to | |
# __main__ attributes | |
current_main = sys.modules['__main__'] | |
if mod_name == "__main__" or mod_name.endswith(".__main__"): | |
return | |
# If this process was forked, __main__ may already be populated | |
if getattr(current_main.__spec__, "name", None) == mod_name: | |
return | |
# Otherwise, __main__ may contain some non-main code where we need to | |
# support unpickling it properly. We rerun it as __mp_main__ and make | |
# the normal __main__ an alias to that | |
old_main_modules.append(current_main) | |
main_module = types.ModuleType("__mp_main__") | |
main_content = runpy.run_module(mod_name, | |
run_name="__mp_main__", | |
alter_sys=True) | |
main_module.__dict__.update(main_content) | |
sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module | |
def _fixup_main_from_path(main_path): | |
# If this process was forked, __main__ may already be populated | |
current_main = sys.modules['__main__'] | |
# Unfortunately, the main ipython launch script historically had no | |
# "if __name__ == '__main__'" guard, so we work around that | |
# by treating it like a __main__.py file | |
# See https://github.com/ipython/ipython/issues/4698 | |
main_name = os.path.splitext(os.path.basename(main_path))[0] | |
if main_name == 'ipython': | |
return | |
# Otherwise, if __file__ already has the setting we expect, | |
# there's nothing more to do | |
if getattr(current_main, '__file__', None) == main_path: | |
return | |
# If the parent process has sent a path through rather than a module | |
# name we assume it is an executable script that may contain | |
# non-main code that needs to be executed | |
old_main_modules.append(current_main) | |
main_module = types.ModuleType("__mp_main__") | |
main_content = runpy.run_path(main_path, | |
run_name="__mp_main__") | |
main_module.__dict__.update(main_content) | |
sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module | |
def import_main_path(main_path): | |
''' | |
Set sys.modules['__main__'] to module at main_path | |
''' | |
_fixup_main_from_path(main_path) | |