Skip to content

gh-116612: Fork before shutdown in test_3_join_in_forked_from_thread - #158130

Open
v0ropaev wants to merge 1 commit into
python:mainfrom
v0ropaev:gh-116612-join-forked-thread
Open

v0ropaev wants to merge 1 commit into
python:mainfrom
v0ropaev:gh-116612-join-forked-thread

Conversation

@v0ropaev

@v0ropaev v0ropaev commented Sep 24, 2026 •

Copy link
Copy Markdown

Closes #116612.

test_3_join_in_forked_from_thread forks from a worker thread while the main thread is running off the end of the script, so whether fork() lands before or after interpreter shutdown begins is a race. When it lands after, the main thread is already marked stopped in the parent, the child inherits that, and mainthread.join() in the child returns for the wrong reason — the test passes without testing anything.

Change

Two lines of substance in Lib/test/test_threading.py:

  • w.join() at the end of the script, so the main thread stays alive until the worker has forked. That is what actually establishes the ordering.
  • a check in the worker that the main thread is still alive immediately before fork(), exiting 3 and printing to stderr if it is not. That is what makes a future regression loud instead of silently green.

What I can and cannot demonstrate

Being straight about this, because it changes how much the diff is worth:

The race does not reproduce unaided on this machine. With w.join() removed, the test still passed 12 runs out of 12 — the fork happens to win every time here. So there is no red-to-green transition to show, and that is exactly why the guard is the pin rather than a failing assertion.

The hazard is real, though, and the guard catches it. Delaying the fork by 0.5 s inside the worker, which is what a loaded machine or a slower interpreter does for free:

configuration result
w.join() present, no delay SUCCESS
w.join() removed, no delay SUCCESS — the race does not fire here
w.join() removed, 0.5 s before fork() FAILURE: main thread stopped before fork()
w.join() present, 0.5 s before fork() SUCCESS

So the main thread genuinely can be gone by the time the worker forks, w.join() is what prevents it, and without the guard that state would have gone unnoticed.

./python.exe -m test test_threading   →  SUCCESS

News

No entry: the change is confined to Lib/test/test_threading.py and nothing user-visible moves. Of the last 40 commits touching Lib/test/, the 8 that touched only Lib/test/ all landed without a Misc/NEWS.d entry. bedevere/news will therefore want the skip news label, which I cannot apply — happy to add an entry under Misc/NEWS.d/next/Tests/ instead if you would rather have one.

…hread

The test script ended at ``w.start()``, so the ``os.fork()`` in
``worker()`` raced the interpreter shutdown that begins as soon as the
main thread falls off the end of the script.

When the fork loses that race it happens inside
``threading._shutdown()``, which marks the main thread as stopped
before joining non-daemon threads.  The child then inherits a main
thread that was already marked as stopped in the parent, so
``mainthread.join()`` in ``joiningfunc()`` returns for the wrong reason
and the test stops checking what its own comment describes: "In the
forked process, the main Thread object must be marked as stopped."

Join ``w`` so that the main thread stays alive until the fork has
happened, and check in ``worker()`` that the main thread really is
still alive before forking.  Without that check the test passes either
way, so losing the join again would go unnoticed.

With ``time.sleep(0.1)`` added at the start of ``worker()`` (the
reproducer from the issue), the fork previously saw
``threading._SHUTTING_DOWN`` true and the main thread already marked
done; it now sees both false, and the main thread handle is marked
done only in the child.
@bedevere-app

bedevere-app Bot commented Sep 24, 2026

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting review tests Tests in the Lib/test dir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test_3_join_in_forked_from_thread forks (and starts thread) at shutdown

1 participant