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
Signal delivered to a subprocess triggers parent's handler #75670
Comments
|
It looks like a signal delivered to multiprocessing's process implicitly created by ProcessPoolExecutor triggers signal handler in the parent:
< kill -s SIGTERM 9999
< kill -s SIGTERM 1000
< kill -s SIGTERM 1000
< kill -s SIGTERM 1000
< kill -s SIGTERM 9999
As you can see, sending SIGTERM into a subprocess somehow triggered a signal handler inside a parent. This is unexpected. |
|
I think either loop's signal handler should not be called from a subprocess or at the very least, os.getpid / os.getpgrp should report correctly. |
|
I get the feeling (without actually investigating) that this is because a fork()-created process inherits all the parent's configuration, including (in this case) signal handlers and whatever file descriptor was configured to receive signal events using signal.set_wakeup_fd(). So the child process, when it receives a signal, also writes on that file descriptor which happens to be the same underlying self-pipe as in the parent. In Python 3.6 it seems there isn't much you can't do against this (well, nothing obvious, in any case). In Python 3.7, you'll have two fixes available in ProcessPoolExecutor (*):
(*) see docs at https://docs.python.org/3.7/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor I would generally recommend using "forkserver" whenever possible, since it eliminates all those inheritance issues by design. |
|
Ouch, yes, that's a tricky bug. This is definitely caused by the way that asyncio internally converts signals into messages along a pipe (well, socket, but same thing), and then after a fork-without-exec the child keeps writing into that pipe. It's exacerbated by asyncio's choice to use the self-pipe as its source of truth about which signals have arrived vs just a wake-up pipe (see [1]), but that's not really the main issue; even without this we'd get spurious wakeups and other unpleasantness. In addition to the workarounds Antoine suggested, it would possibly make sense for forked children to disable any wakeup_fd, perhaps in PyOS_AfterFork or by adding a getpid() check to the C level signal handler. I can't think of any cases where you actually want to processes to share the same wake-up fd. And even if this isn't fixed at that level, it would make sense for asyncio to use the new atfork module to do something similar for asyncio specifically. Also relevant: python/asyncio#347 [1] dabeaz/curio#118 |
|
Can you suggest an alternative to ProcessPoolExecutor for 3.6? |
|
You may switch to multiprocessing.Pool (with the "forkserver" method). Otherwise, you could workaround it by executing a function on all workers that will reset the signal configuration. To maximize the chances that it does get executed on all workers, you could add a sleep() call inside it... |
|
It might be possible to create ProcessPoolExecutor and get it to spawn all the workers *before* you start the asyncio loop. It looks like ProcessPoolExecutor delays spawning workers until the first piece of work is submitted, but at that point it spawns all of them immediately, so something like this might work: executor = ProcessPoolExecutor(...)
executor.submit(lambda: None).wait()
with asyncio.get_event_loop() as loop:
loop.run_until_complete(...) |
|
Duplicate of #94454 |
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: