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