spawn.py revision 7d2d43c0b15b8062c9b5d672a78e653abe2e1d91
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 12import pickle 13import sys 14 15from . import process 16from . import util 17from . import popen 18 19__all__ = ['_main', 'freeze_support', 'set_executable', 'get_executable', 20 'get_preparation_data', 'get_command_line', 'import_main_path'] 21 22# 23# _python_exe is the assumed path to the python executable. 24# People embedding Python want to modify it. 25# 26 27if sys.platform != 'win32': 28 WINEXE = False 29 WINSERVICE = False 30else: 31 WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False)) 32 WINSERVICE = sys.executable.lower().endswith("pythonservice.exe") 33 34if WINSERVICE: 35 _python_exe = os.path.join(sys.exec_prefix, 'python.exe') 36else: 37 _python_exe = sys.executable 38 39def set_executable(exe): 40 global _python_exe 41 _python_exe = exe 42 43def get_executable(): 44 return _python_exe 45 46# 47# 48# 49 50def is_forking(argv): 51 ''' 52 Return whether commandline indicates we are forking 53 ''' 54 if len(argv) >= 2 and argv[1] == '--multiprocessing-fork': 55 return True 56 else: 57 return False 58 59 60def freeze_support(): 61 ''' 62 Run code for process object if this in not the main process 63 ''' 64 if is_forking(sys.argv): 65 main() 66 sys.exit() 67 68 69def get_command_line(**kwds): 70 ''' 71 Returns prefix of command line used for spawning a child process 72 ''' 73 if getattr(sys, 'frozen', False): 74 return [sys.executable, '--multiprocessing-fork'] 75 else: 76 prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)' 77 prog %= ', '.join('%s=%r' % item for item in kwds.items()) 78 opts = util._args_from_interpreter_flags() 79 return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork'] 80 81 82def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): 83 ''' 84 Run code specifed by data received over pipe 85 ''' 86 assert is_forking(sys.argv) 87 if sys.platform == 'win32': 88 import msvcrt 89 from .reduction import steal_handle 90 new_handle = steal_handle(parent_pid, pipe_handle) 91 fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY) 92 else: 93 from . import semaphore_tracker 94 semaphore_tracker._semaphore_tracker_fd = tracker_fd 95 fd = pipe_handle 96 exitcode = _main(fd) 97 sys.exit(exitcode) 98 99 100def _main(fd): 101 with os.fdopen(fd, 'rb', closefd=True) as from_parent: 102 process.current_process()._inheriting = True 103 try: 104 preparation_data = pickle.load(from_parent) 105 prepare(preparation_data) 106 self = pickle.load(from_parent) 107 finally: 108 del process.current_process()._inheriting 109 return self._bootstrap() 110 111 112def _check_not_importing_main(): 113 if getattr(process.current_process(), '_inheriting', False): 114 raise RuntimeError(''' 115 An attempt has been made to start a new process before the 116 current process has finished its bootstrapping phase. 117 118 This probably means that you are not using fork to start your 119 child processes and you have forgotten to use the proper idiom 120 in the main module: 121 122 if __name__ == '__main__': 123 freeze_support() 124 ... 125 126 The "freeze_support()" line can be omitted if the program 127 is not going to be frozen to produce an executable.''') 128 129 130def get_preparation_data(name): 131 ''' 132 Return info about parent needed by child to unpickle process object 133 ''' 134 _check_not_importing_main() 135 d = dict( 136 log_to_stderr=util._log_to_stderr, 137 authkey=process.current_process().authkey, 138 ) 139 140 if util._logger is not None: 141 d['log_level'] = util._logger.getEffectiveLevel() 142 143 sys_path=sys.path.copy() 144 try: 145 i = sys_path.index('') 146 except ValueError: 147 pass 148 else: 149 sys_path[i] = process.ORIGINAL_DIR 150 151 d.update( 152 name=name, 153 sys_path=sys_path, 154 sys_argv=sys.argv, 155 orig_dir=process.ORIGINAL_DIR, 156 dir=os.getcwd(), 157 start_method=popen.get_start_method(), 158 ) 159 160 if sys.platform != 'win32' or (not WINEXE and not WINSERVICE): 161 main_path = getattr(sys.modules['__main__'], '__file__', None) 162 if not main_path and sys.argv[0] not in ('', '-c'): 163 main_path = sys.argv[0] 164 if main_path is not None: 165 if (not os.path.isabs(main_path) and 166 process.ORIGINAL_DIR is not None): 167 main_path = os.path.join(process.ORIGINAL_DIR, main_path) 168 d['main_path'] = os.path.normpath(main_path) 169 170 return d 171 172# 173# Prepare current process 174# 175 176old_main_modules = [] 177 178def prepare(data): 179 ''' 180 Try to get current process ready to unpickle process object 181 ''' 182 if 'name' in data: 183 process.current_process().name = data['name'] 184 185 if 'authkey' in data: 186 process.current_process().authkey = data['authkey'] 187 188 if 'log_to_stderr' in data and data['log_to_stderr']: 189 util.log_to_stderr() 190 191 if 'log_level' in data: 192 util.get_logger().setLevel(data['log_level']) 193 194 if 'sys_path' in data: 195 sys.path = data['sys_path'] 196 197 if 'sys_argv' in data: 198 sys.argv = data['sys_argv'] 199 200 if 'dir' in data: 201 os.chdir(data['dir']) 202 203 if 'orig_dir' in data: 204 process.ORIGINAL_DIR = data['orig_dir'] 205 206 if 'start_method' in data: 207 popen.set_start_method(data['start_method'], start_helpers=False) 208 209 if 'main_path' in data: 210 import_main_path(data['main_path']) 211 212 213def import_main_path(main_path): 214 ''' 215 Set sys.modules['__main__'] to module at main_path 216 ''' 217 # XXX (ncoghlan): The following code makes several bogus 218 # assumptions regarding the relationship between __file__ 219 # and a module's real name. See PEP 302 and issue #10845 220 if getattr(sys.modules['__main__'], '__file__', None) == main_path: 221 return 222 223 main_name = os.path.splitext(os.path.basename(main_path))[0] 224 if main_name == '__init__': 225 main_name = os.path.basename(os.path.dirname(main_path)) 226 227 if main_name == '__main__': 228 main_module = sys.modules['__main__'] 229 main_module.__file__ = main_path 230 elif main_name != 'ipython': 231 # Main modules not actually called __main__.py may 232 # contain additional code that should still be executed 233 import importlib 234 import types 235 236 if main_path is None: 237 dirs = None 238 elif os.path.basename(main_path).startswith('__init__.py'): 239 dirs = [os.path.dirname(os.path.dirname(main_path))] 240 else: 241 dirs = [os.path.dirname(main_path)] 242 243 assert main_name not in sys.modules, main_name 244 sys.modules.pop('__mp_main__', None) 245 # We should not try to load __main__ 246 # since that would execute 'if __name__ == "__main__"' 247 # clauses, potentially causing a psuedo fork bomb. 248 loader = importlib.find_loader(main_name, path=dirs) 249 main_module = types.ModuleType(main_name) 250 try: 251 loader.init_module_attrs(main_module) 252 except AttributeError: # init_module_attrs is optional 253 pass 254 main_module.__name__ = '__mp_main__' 255 code = loader.get_code(main_name) 256 exec(code, main_module.__dict__) 257 258 old_main_modules.append(sys.modules['__main__']) 259 sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module 260