|
11 | 11 | import os |
12 | 12 | import pickle |
13 | 13 | import sys |
| 14 | +import runpy |
| 15 | +import types |
14 | 16 |
|
15 | 17 | from . import get_start_method, set_start_method |
16 | 18 | from . import process |
@@ -157,15 +159,19 @@ def get_preparation_data(name): |
157 | 159 | start_method=get_start_method(), |
158 | 160 | ) |
159 | 161 |
|
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] |
| 162 | + # Figure out whether to initialise main in the subprocess as a module |
| 163 | + # or through direct execution (or to leave it alone entirely) |
| 164 | + main_module = sys.modules['__main__'] |
| 165 | + main_mod_name = getattr(main_module.__spec__, "name", None) |
| 166 | + if main_mod_name is not None: |
| 167 | + d['init_main_from_name'] = main_mod_name |
| 168 | + elif sys.platform != 'win32' or (not WINEXE and not WINSERVICE): |
| 169 | + main_path = getattr(main_module, '__file__', None) |
164 | 170 | if main_path is not None: |
165 | 171 | if (not os.path.isabs(main_path) and |
166 | 172 | process.ORIGINAL_DIR is not None): |
167 | 173 | main_path = os.path.join(process.ORIGINAL_DIR, main_path) |
168 | | - d['main_path'] = os.path.normpath(main_path) |
| 174 | + d['init_main_from_path'] = os.path.normpath(main_path) |
169 | 175 |
|
170 | 176 | return d |
171 | 177 |
|
@@ -206,55 +212,68 @@ def prepare(data): |
206 | 212 | if 'start_method' in data: |
207 | 213 | set_start_method(data['start_method']) |
208 | 214 |
|
209 | | - if 'main_path' in data: |
210 | | - import_main_path(data['main_path']) |
| 215 | + if 'init_main_from_name' in data: |
| 216 | + _fixup_main_from_name(data['init_main_from_name']) |
| 217 | + elif 'init_main_from_path' in data: |
| 218 | + _fixup_main_from_path(data['init_main_from_path']) |
| 219 | + |
| 220 | +# Multiprocessing module helpers to fix up the main module in |
| 221 | +# spawned subprocesses |
| 222 | +def _fixup_main_from_name(mod_name): |
| 223 | + # __main__.py files for packages, directories, zip archives, etc, run |
| 224 | + # their "main only" code unconditionally, so we don't even try to |
| 225 | + # populate anything in __main__, nor do we make any changes to |
| 226 | + # __main__ attributes |
| 227 | + current_main = sys.modules['__main__'] |
| 228 | + if mod_name == "__main__" or mod_name.endswith(".__main__"): |
| 229 | + return |
| 230 | + |
| 231 | + # If this process was forked, __main__ may already be populated |
| 232 | + if getattr(current_main.__spec__, "name", None) == mod_name: |
| 233 | + return |
| 234 | + |
| 235 | + # Otherwise, __main__ may contain some non-main code where we need to |
| 236 | + # support unpickling it properly. We rerun it as __mp_main__ and make |
| 237 | + # the normal __main__ an alias to that |
| 238 | + old_main_modules.append(current_main) |
| 239 | + main_module = types.ModuleType("__mp_main__") |
| 240 | + main_content = runpy.run_module(mod_name, |
| 241 | + run_name="__mp_main__", |
| 242 | + alter_sys=True) |
| 243 | + main_module.__dict__.update(main_content) |
| 244 | + sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module |
| 245 | + |
| 246 | + |
| 247 | +def _fixup_main_from_path(main_path): |
| 248 | + # If this process was forked, __main__ may already be populated |
| 249 | + current_main = sys.modules['__main__'] |
| 250 | + |
| 251 | + # Unfortunately, the main ipython launch script historically had no |
| 252 | + # "if __name__ == '__main__'" guard, so we work around that |
| 253 | + # by treating it like a __main__.py file |
| 254 | + # See https://github.com/ipython/ipython/issues/4698 |
| 255 | + main_name = os.path.splitext(os.path.basename(main_path))[0] |
| 256 | + if main_name == 'ipython': |
| 257 | + return |
| 258 | + |
| 259 | + # Otherwise, if __file__ already has the setting we expect, |
| 260 | + # there's nothing more to do |
| 261 | + if getattr(current_main, '__file__', None) == main_path: |
| 262 | + return |
| 263 | + |
| 264 | + # If the parent process has sent a path through rather than a module |
| 265 | + # name we assume it is an executable script that may contain |
| 266 | + # non-main code that needs to be executed |
| 267 | + old_main_modules.append(current_main) |
| 268 | + main_module = types.ModuleType("__mp_main__") |
| 269 | + main_content = runpy.run_path(main_path, |
| 270 | + run_name="__mp_main__") |
| 271 | + main_module.__dict__.update(main_content) |
| 272 | + sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module |
211 | 273 |
|
212 | 274 |
|
213 | 275 | def import_main_path(main_path): |
214 | 276 | ''' |
215 | 277 | Set sys.modules['__main__'] to module at main_path |
216 | 278 | ''' |
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 | | - main_module = types.ModuleType(main_name) |
249 | | - # XXX Use a target of main_module? |
250 | | - spec = importlib.find_spec(main_name, path=dirs) |
251 | | - if spec is None: |
252 | | - raise ImportError(name=main_name) |
253 | | - methods = importlib._bootstrap._SpecMethods(spec) |
254 | | - methods.init_module_attrs(main_module) |
255 | | - main_module.__name__ = '__mp_main__' |
256 | | - code = spec.loader.get_code(main_name) |
257 | | - exec(code, main_module.__dict__) |
258 | | - |
259 | | - old_main_modules.append(sys.modules['__main__']) |
260 | | - sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module |
| 279 | + _fixup_main_from_path(main_path) |
0 commit comments