This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: multiprocessing behavior combining daemon with non-daemon children inconsistent with threading
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.6, Python 3.5, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: davin, jnoller, josh.r, rpcope1, sbt
Priority: normal Keywords:

Created on 2016-03-24 03:17 by josh.r, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
testmpdaemon.py josh.r, 2016-03-24 03:17 bug demo code
Messages (2)
msg262324 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2016-03-24 03:17
Unclear if this is just unclear docs, or incorrect behavior.

Per this Stack Overflow question ( https://stackoverflow.com/questions/36191447/why-doesnt-the-daemon-program-exit-without-join ), you get some rather odd behavior when you have both daemon and non-daemon child processes. In the case described, the following steps occur:

1. A daemon Process is launched which prints a message, waits two seconds, then prints a second message
2. The main process sleeps one second
3. A non-daemon process is launched which behaves the same as the daemon process, but sleeps six seconds before the second message.
4. The main process completes

The expected behavior (to my mind and the questioner on SO) is that since there is a non-daemon process running, the "process family" should stay alive until the non-daemon process finishes, which gives the daemon process time to wake up and print its second message (five seconds before the non-daemon process wakes to finish its "work"). But in fact, the atexit function used for cleanup in multiprocessing first calls .terminate() on all daemon children before join-ing all children. So the moment the main process completes, it immediately terminates the daemon child, even though the "process family" is still alive.

This seems counter-intuitive; in the threading case, which multiprocessing is supposed to emulate, all non-daemon threads are equivalent, so no daemon threads are cleaned until the last non-daemon thread exits. To match the threading behavior, it seems like the cleanup code should first join all the non-daemon children, then terminate the daemon children, then join the daemon children.

This would change the code here (
https://hg.python.org/cpython/file/3.5/Lib/multiprocessing/util.py#l303 ) from:

            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()

            for p in active_children():
                info('calling join() for process %s', p.name)
                p.join()

to:

            # Wait on non-daemons first
            for p in active_children():
                info('calling join() for process %s', p.name)
                if not p.daemon:
                    p.join()

            # Terminate and clean up daemons now that non-daemons done
            for p in active_children():
                if p.daemon:
                    info('calling terminate() for daemon %s', p.name)
                    p._popen.terminate()
                    info('calling join() for process %s', p.name)
                    p.join()


I've attached repro code to demonstrate; using multiprocessing, the daemon never prints its exiting message, while switching to multiprocessing.dummy (backed by threading) correctly prints the exit message.
msg262325 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2016-03-24 03:21
Oops, left the info log in my replacement code for the non-daemon case outside the if not daemon: block; it should be inside (since it's not joining the daemon threads).
History
Date User Action Args
2022-04-11 14:58:28adminsetgithub: 70820
2016-03-29 06:51:02rpcope1setnosy: + rpcope1
2016-03-29 02:40:33davinsetnosy: + davin
2016-03-24 08:54:16SilentGhostsetnosy: + jnoller, sbt
2016-03-24 03:21:10josh.rsetmessages: + msg262325
2016-03-24 03:17:15josh.rcreate