-
-
Notifications
You must be signed in to change notification settings - Fork 30.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle a non-importable __main__ in multiprocessing #64145
Comments
Here is a simple python program that uses the new forkserver feature introduced in 3.4b1: name: checkforkserver.py def do(i):
print(i, os.getpid())
def test_forkserver():
mp = multiprocessing.get_context('forkserver')
mp.Pool(2).map(do, range(3))
if __name__ == "__main__":
test_forkserver()
""" When running this using the "python check_forkserver.py" command everything works as expected. When running this using the nosetests launcher ("nosetests -s check_forkserver.py"), I get: """
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/forkserver.py", line 141, in main
spawn.import_main_path(main_path)
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 252, in import_main_path
methods.init_module_attrs(main_module)
File "<frozen importlib._bootstrap>", line 1051, in init_module_attrs
AttributeError: 'NoneType' object has no attribute 'loader'
""" Indeed, the spec variable in multiprocessing/spawn.py's import_main_path If I copy or symlink or renamed the "nosetests" script as "nosetests.py" in the same folder, this works as expected. I am not familiar enough with the importlib machinery to suggest a fix for this bug. Also there is a typo in the comment: "causing a psuedo fork bomb" => "causing a pseudo fork bomb". Note: I am running CPython head updated today. |
This sounds related to the ModuleSpec changes. |
The returning of None means that importlib.find_spec() didn't find the spec/loader for the specified module. So the question is exactly what module is being passed to importlib.find_spec() and why isn't it finding a spec/loader for that module. Did this code work in Python 3.3? |
I see Oliver says he is testing a new forkserver feature from 3.4b1, so it might not necessarily be importlib's fault then. Does using the old importlib.find_loader() approach work? |
The module is the
This code did not exist in Python 3.3. |
So at the bare minimum, the multiprocessing code should raise an ImportError when it can't find the spec for the module to help debug this kind of thing. Also that typo should get fixed. Second, there is no way that 'nosetests' will ever succeed as an import since, as Oliver pointed out, it doesn't end in '.py' or any other identifiable way for a finder to know it can handle the file. So this is not a bug and simply a side-effect of how import works. The only way around it would be to symlink nosetests to nosetests.py or to somehow pre-emptively set up 'nosetests' for supported importing. |
I guess this is a case where we should not be trying to import the main module. The code for determining the path of the main module (if any) is rather crufty. What is sys.modules['__main__'] and sys.modules['__main__'].__file__ if you run under nose? |
I agree that a failure to lookup the module should raise an explicit exception.
I don't agree that (unix) Python programs that don't end with ".py" should be modified to have multiprocessing work correctly. I think it should be the multiprocessing responsibility to transparently find out how to spawn the new process independently of the fact that the program ends in '.py' or not. Note: the fork mode works always under unix (with or without the ".py" extension). The spawn mode always work under windows as AFAIK there is no way to have Python programs that don't end in .py under windows and furthermore I think multiprocessing does execute the __main__ under windows (but I haven't tested if it's still the case in Python HEAD). |
$ cat check_stuff.py
import sys def test_main():
print("sys.modules['__main__']=%r"
% sys.modules['__main__'])
print("sys.modules['__main__'].__file__=%r"
% sys.modules['__main__'].__file__)
if __name__ == '__main__':
test_main()
(pyhead) ogrisel@is146148:~/tmp$ python check_stuff.py
sys.modules['__main__']=<module '__main__' from 'check_stuff.py'>
sys.modules['__main__'].__file__='check_stuff.py'
(pyhead) ogrisel@is146148:~/tmp$ nosetests -s check_stuff.py
sys.modules['__main__']=<module '__main__' from '/volatile/ogrisel/envs/pyhead/bin/nosetests'>
sys.modules['__main__'].__file__='/volatile/ogrisel/envs/pyhead/bin/nosetests'
. Ran 1 test in 0.001s OK |
Note however that the problem is not specific to nose. If I rename my initial 'check_forserver.py' script to 'check_forserver', add the '#!/usr/bin/env python' header and make it 'chmod +x' I get the same crash. So the problem is related to the fact that under posix, valid Python programs can be executable scripts without the '.py' extension. |
Here is a patch that uses Apparently it fixes the issue on my box but I am not sure whether this is the correct way to do it. |
Rerunning main in the subprocess has always been a slightly dubious feature |
New changeset cea42629ddf5 by Brett Cannon in branch 'default': |
Created http://bugs.python.org/issue19978 to track using runpy.run_path in 3.5. |
Why has this issue been closed? Won't the spawn and forkserver mode work in Python 3.4 for Python program started by a Python script (which is probably the majority of programs written in Python under unix)? Is there any reason not to use the |
Multiple questions from Oliver to answer.
Because the decided issue of this bug -- raising AttributeError over ImportError -- was fixed.
The semantics are not going to change in python 3.4 and will just stay as they were in Python 3.3.
There are two reasons. One is that the imp module is deprecated in Python 3.4 (http://docs.python.org/3.4/library/imp.html#module-imp). Two is that temporarily inserting for a single release a solution that will simply be ripped out in the following release is just asking for trouble. Someone will use the temporary fix in some way in production code and then be shocked when the better solution doesn't work in exactly the same way. It's best to simply wait until 3.5 has the proper solution available. I know it's frustrating to either name your scripts with a .py until Python 3.5 comes out or just wait until 3.5, but we can't risk a hacky solution for a single release as users will be living with the hack for several years. |
Well the semantics do change: in Python 3.3 the spawn and forkserver modes did not exist at all. The "spawn" mode existed but only implicitly and only under Windows. So Python 3.4 is introducing a new feature for POSIX systems that will only work in the rare cases where the Python program is launched by a ".py" ending script. Would running the |
I'm sorry, Oliver, you are simply going to have to wait for Python 3.5 at this point to get the new semantics you want. |
Side note: it's Olivier, not Oliver. |
I can wait (or monkey-patch the stuff I need as a temporary workaround in my code). My worry is that Python 3.4 will introduce a new feature that is very crash-prone. Take this simple program that uses the newly introduced filename: mytool def compute_stuff(i):
# in real life you could use a lib that uses threads
# like cuda and that would crash with the default 'fork'
# mode under POSIX
return i ** 2
if __name__ == "__main__":
freeze_support()
ctx = get_context('spawn')
ctx.Pool(4).map(compute_stuff, range(8)) """ If you chmod +x this file and run it with ./mytool, the user will get an infinitely running process that keeps displaying on stderr: """
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 96, in spawn_main
exitcode = _main(fd)
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 105, in _main
prepare(preparation_data)
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 210, in prepare
import_main_path(data['main_path'])
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 256, in import_main_path
raise ImportError(name=main_name)
ImportError
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 96, in spawn_main
exitcode = _main(fd)
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 105, in _main
prepare(preparation_data)
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 210, in prepare
import_main_path(data['main_path'])
File "/opt/Python-HEAD/lib/python3.4/multiprocessing/spawn.py", line 256, in import_main_path
raise ImportError(name=main_name)
ImportError
...
""" until the user kills the process. Is there really nothing we can do to avoid releasing Python 3.4 with this bug? |
Let's reopen, shall we? If not for 3.4, at least for 3.5. It's likely that multiprocessing needs a __main__ simply because it needs a way to replicate the parent process' state in the child (for example, the set of imported modules, the logging configuration, etc.). Perhaps Richard can elaborate. But, AFAIU, the __main__ could be imported as a script rather than a "proper" module from sys.path. |
bpo-19700 isn't quite finished, but I believe it is finished enough to let me get this working properly. |
So there are really two situations:
This should really be detected in get_preparation_data() in the parent process so that import_main_path() does not get called in the child process.
|
Bumping the priority on this, as multiprocessing is currently creating invalid child processes by failing to set __main__.__spec__ appropriately. The attached patch is designed to get us started down that path. It's currently broken, but I need feedback from folks that know the multiprocessing code better than I do in order to know where best to start poking and prodding. With the patch, invoking regrtest directly still works:
But relying on module execution fails: I appear to be somehow getting child processes where __main__.__file__ is set, but __main__.__spec__ is not. |
With the restructuring in my patch, it would be easy enough to move the "early return" cases from the _fixup_main_* functions to instead be "don't set the variable" cases in get_preparation_data. |
That seems to be true for the __main__ module even when multiprocessing is not involved. Running a file /tmp/foo.py containing import sys
print(sys.modules['__main__'].__spec__, sys.modules['__main__'].__file__) I get output
I am confused by why you would ever want to load by module name rather than file name. What problem would that fix? If the idea is just to support importing a main module without a .py extension, isn't __file__ good enough? |
Scripts (whether in source form or precompiled) work via direct execution, Historically, it was a hard problem to solve, since even the parent process I also have an idea as to what may be wrong with my patch - I'm going to |
I created a test suite to ensure that all the various cases were handled correctly by the eventual patch (it doesn't test some of the namespace package related edge cases, but they devolve to normal module execution in terms of the final state of __main__, and that's covered by these tests). |
Current work in progress patch. The existing multiprocessing tests all pass, but the new main handling tests fail. The fork start_method passes all the tests |
Updated test that handles timeouts better. I also realised the current test failures are due to an error in the test design - the "failing" cases are ones where we deliberately *don't* rerun __main__ because the entire __main__.py file is assumed to be inside an implicit __main__-only guard. So the code changes should be complete, I just need to figure out a way to tweak the tests appropriately. |
I applied issue19946_pep_451_multiprocessing_v2.diff and I confirm that it fixes the problem that I reported initially. |
OK, fixed test case attached. Turns out the ipython workaround test was completely wrong and never even loaded multiprocessing, and hence always passed, even with the workaround disabled. So I fixed that test case, and used the same approach for the zipfile, directory and package tests. I also fixed the submodule test to check that explicit relative imports work properly from __mp_main__ in the child processes. With this updated test cast, the v2 patch handles everything correctly, but there are 4 failures on Linux without the patch. Specifically:
The former case is the one Olivier reported in this issue. It's a new case for 3.4, since the spawn start method was previously only available on Windows, where scripts always have an extension. The latter edge case is the one my "XXX (ncoghlan): The following code makes several bogus assumptions regarding the relationship between __file__ and a module's real name." comment was about. I believe we could actually adjust earlier versions to handle things as well as this new PEP-451 based approach (by using a combination of __package__ and __file__ rather than __spec__), but that's much harder for me to work on in those versions where the "spawn" start method is only available on Windows :) |
New changeset b6d6f3b4b100 by Nick Coghlan in branch 'default': |
Thanks for your hard work Nick! |
The commit broken a couple of buildbots like all Windows bots and OpenIndiana. |
The problem on Windows at least is that the skips for the 'fork' and 'forkserver' start methods aren't firing due to setUpClass being improperly set up in MultiProcessingCmdLineMixin: it's not decorated as a classmethod and the 'u' is lower-case instead of upper. Just fixing that makes for some unusual output ("skipped '"fork" start method not available'" with no indication of which test was skipped) and a variable number of tests depending on available start methods, so a better fix is to just do the check in setUp. Unrelated to the failure, but we're also in the process of moving away from using test_main(), preferring unittest.main(). The attached patch addresses both, passes on Windows and Linux, and I suspect should help on OpenIndiana as well judging by the tracebacks it's giving. |
New changeset 460961e80e31 by Nick Coghlan in branch 'default': |
The OpenIndiana tests are still failing. OpenIndiana doesn't support forkserver because it doesn't implement the send handle feature. The patch skips the forkserver tests if HAVE_SEND_HANDLE is false. |
I think that needs to be fixed on the multiprocessing side rather than just There may also be docs implications in describing which methods are |
On 19/12/2013 10:00 pm, Nick Coghlan wrote:
If by "concrete context" you mean _concrete_contexts['forkserver'], then ctx = multiprocessing.get_context('forkserver') then this will raise ValueError if the forkserver method is not
to check if it is available. |
Ah, I should have looked more closely at the docs to see if there was a public API for that before poking around in the package internals. In that case, I suggest we change this bit in the test: # We look inside the context module to find out which
# start methods we can check
from multiprocessing.context import _concrete_contexts to use the appropriate public API: # Need to know which start methods we should test
import multiprocessing
AVAILABLE_START_METHODS = set(multiprocessing.get_all_start_methods()) And then adjust the skip check to look in AVAILABLE_START_METHODS rather than _concrete_contexts. I'll make that change tonight if nobody beats me to it. |
New changeset 00d09afb57ca by Nick Coghlan in branch 'default': |
Pending a clean bill of health from the stable buildbots :) |
Now passing on all the stable buildbots (the two red Windows bots are for other issues, such as bpo-15599 for the threaded import test failure) |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: