1# 2# Code used to start processes when using the spawn or forkserver 3# start methods. 4# 5# multiprocessing/spawn.py 6# 7# Copyright (c) 2006-2008, R Oudkerk 8# Licensed to PSF under a Contributor Agreement. 9# 10 11import os 12try: 13 import dill as pickle 14except ImportError: 15 import pickle 16import sys 17import runpy 18import types 19 20from . import get_start_method, set_start_method 21from . import process 22from . import util 23 24__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable', 25 'get_preparation_data', 'get_command_line', 'import_main_path'] 26 27# 28# _python_exe is the assumed path to the python executable. 29# People embedding Python want to modify it. 30# 31 32if sys.platform != 'win32': 33 WINEXE = False 34 WINSERVICE = False 35else: 36 WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False)) 37 WINSERVICE = sys.executable.lower().endswith("pythonservice.exe") 38 39if WINSERVICE: 40 _python_exe = os.path.join(sys.exec_prefix, 'python.exe') 41else: 42 _python_exe = sys.executable 43 44def set_executable(exe): 45 global _python_exe 46 _python_exe = exe 47 48def get_executable(): 49 return _python_exe 50 51# 52# 53# 54 55def is_forking(argv): 56 ''' 57 Return whether commandline indicates we are forking 58 ''' 59 if len(argv) >= 2 and argv[1] == '--multiprocessing-fork': 60 return True 61 else: 62 return False 63 64 65def freeze_support(): 66 ''' 67 Run code for process object if this in not the main process 68 ''' 69 if is_forking(sys.argv): 70 kwds = {} 71 for arg in sys.argv[2:]: 72 name, value = arg.split('=') 73 if value == 'None': 74 kwds[name] = None 75 else: 76 kwds[name] = int(value) 77 spawn_main(**kwds) 78 sys.exit() 79 80 81def get_command_line(**kwds): 82 ''' 83 Returns prefix of command line used for spawning a child process 84 ''' 85 if getattr(sys, 'frozen', False): 86 return ([sys.executable, '--multiprocessing-fork'] + 87 ['%s=%r' % item for item in kwds.items()]) 88 else: 89 prog = 'from multiprocess.spawn import spawn_main; spawn_main(%s)' 90 prog %= ', '.join('%s=%r' % item for item in kwds.items()) 91 opts = util._args_from_interpreter_flags() 92 return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork'] 93 94 95def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): 96 ''' 97 Run code specifed by data received over pipe 98 ''' 99 assert is_forking(sys.argv) 100 if sys.platform == 'win32': 101 import msvcrt 102 from .reduction import steal_handle 103 new_handle = steal_handle(parent_pid, pipe_handle) 104 fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY) 105 else: 106 from . import semaphore_tracker 107 semaphore_tracker._semaphore_tracker._fd = tracker_fd 108 fd = pipe_handle 109 exitcode = _main(fd) 110 sys.exit(exitcode) 111 112 113def _main(fd): 114 with os.fdopen(fd, 'rb', closefd=True) as from_parent: 115 process.current_process()._inheriting = True 116 try: 117 preparation_data = pickle.load(from_parent) 118 prepare(preparation_data) 119 self = pickle.load(from_parent) 120 finally: 121 del process.current_process()._inheriting 122 return self._bootstrap() 123 124 125def _check_not_importing_main(): 126 if getattr(process.current_process(), '_inheriting', False): 127 raise RuntimeError(''' 128 An attempt has been made to start a new process before the 129 current process has finished its bootstrapping phase. 130 131 This probably means that you are not using fork to start your 132 child processes and you have forgotten to use the proper idiom 133 in the main module: 134 135 if __name__ == '__main__': 136 freeze_support() 137 ... 138 139 The "freeze_support()" line can be omitted if the program 140 is not going to be frozen to produce an executable.''') 141 142 143def get_preparation_data(name): 144 ''' 145 Return info about parent needed by child to unpickle process object 146 ''' 147 _check_not_importing_main() 148 d = dict( 149 log_to_stderr=util._log_to_stderr, 150 authkey=process.current_process().authkey, 151 ) 152 153 if util._logger is not None: 154 d['log_level'] = util._logger.getEffectiveLevel() 155 156 sys_path=sys.path.copy() 157 try: 158 i = sys_path.index('') 159 except ValueError: 160 pass 161 else: 162 sys_path[i] = process.ORIGINAL_DIR 163 164 d.update( 165 name=name, 166 sys_path=sys_path, 167 sys_argv=sys.argv, 168 orig_dir=process.ORIGINAL_DIR, 169 dir=os.getcwd(), 170 start_method=get_start_method(), 171 ) 172 173 # Figure out whether to initialise main in the subprocess as a module 174 # or through direct execution (or to leave it alone entirely) 175 main_module = sys.modules['__main__'] 176 main_mod_name = getattr(main_module.__spec__, "name", None) 177 if main_mod_name is not None: 178 d['init_main_from_name'] = main_mod_name 179 elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE): 180 main_path = getattr(main_module, '__file__', None) 181 if main_path is not None: 182 if (not os.path.isabs(main_path) and 183 process.ORIGINAL_DIR is not None): 184 main_path = os.path.join(process.ORIGINAL_DIR, main_path) 185 d['init_main_from_path'] = os.path.normpath(main_path) 186 187 return d 188 189# 190# Prepare current process 191# 192 193old_main_modules = [] 194 195def prepare(data): 196 ''' 197 Try to get current process ready to unpickle process object 198 ''' 199 if 'name' in data: 200 process.current_process().name = data['name'] 201 202 if 'authkey' in data: 203 process.current_process().authkey = data['authkey'] 204 205 if 'log_to_stderr' in data and data['log_to_stderr']: 206 util.log_to_stderr() 207 208 if 'log_level' in data: 209 util.get_logger().setLevel(data['log_level']) 210 211 if 'sys_path' in data: 212 sys.path = data['sys_path'] 213 214 if 'sys_argv' in data: 215 sys.argv = data['sys_argv'] 216 217 if 'dir' in data: 218 os.chdir(data['dir']) 219 220 if 'orig_dir' in data: 221 process.ORIGINAL_DIR = data['orig_dir'] 222 223 if 'start_method' in data: 224 set_start_method(data['start_method']) 225 226 if 'init_main_from_name' in data: 227 _fixup_main_from_name(data['init_main_from_name']) 228 elif 'init_main_from_path' in data: 229 _fixup_main_from_path(data['init_main_from_path']) 230 231# Multiprocessing module helpers to fix up the main module in 232# spawned subprocesses 233def _fixup_main_from_name(mod_name): 234 # __main__.py files for packages, directories, zip archives, etc, run 235 # their "main only" code unconditionally, so we don't even try to 236 # populate anything in __main__, nor do we make any changes to 237 # __main__ attributes 238 current_main = sys.modules['__main__'] 239 if mod_name == "__main__" or mod_name.endswith(".__main__"): 240 return 241 242 # If this process was forked, __main__ may already be populated 243 if getattr(current_main.__spec__, "name", None) == mod_name: 244 return 245 246 # Otherwise, __main__ may contain some non-main code where we need to 247 # support unpickling it properly. We rerun it as __mp_main__ and make 248 # the normal __main__ an alias to that 249 old_main_modules.append(current_main) 250 main_module = types.ModuleType("__mp_main__") 251 main_content = runpy.run_module(mod_name, 252 run_name="__mp_main__", 253 alter_sys=True) 254 main_module.__dict__.update(main_content) 255 sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module 256 257 258def _fixup_main_from_path(main_path): 259 # If this process was forked, __main__ may already be populated 260 current_main = sys.modules['__main__'] 261 262 # Unfortunately, the main ipython launch script historically had no 263 # "if __name__ == '__main__'" guard, so we work around that 264 # by treating it like a __main__.py file 265 # See https://github.com/ipython/ipython/issues/4698 266 main_name = os.path.splitext(os.path.basename(main_path))[0] 267 if main_name == 'ipython': 268 return 269 270 # Otherwise, if __file__ already has the setting we expect, 271 # there's nothing more to do 272 if getattr(current_main, '__file__', None) == main_path: 273 return 274 275 # If the parent process has sent a path through rather than a module 276 # name we assume it is an executable script that may contain 277 # non-main code that needs to be executed 278 old_main_modules.append(current_main) 279 main_module = types.ModuleType("__mp_main__") 280 main_content = runpy.run_path(main_path, 281 run_name="__mp_main__") 282 main_module.__dict__.update(main_content) 283 sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module 284 285 286def import_main_path(main_path): 287 ''' 288 Set sys.modules['__main__'] to module at main_path 289 ''' 290 _fixup_main_from_path(main_path) 291