Skip to content
22 changes: 22 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6844,6 +6844,28 @@ def f(x): return x*x
self.assertEqual("332833500", out.decode('utf-8').strip())
self.assertFalse(err, msg=err.decode('utf-8'))

@support.requires_fork()
def test_forked_not_started(self):
# gh-134381
# Ensure that a forked parent process, which has not been started yet,
# can be initiated within the child process.

ctx = multiprocessing.get_context("fork") # local “fork” cont
q = ctx.Queue()
t = threading.Thread(target=lambda: q.put("done"), daemon=True)

def child():
t.start()
t.join()

p = ctx.Process(target=child)
p.start()
p.join(support.SHORT_TIMEOUT)

self.assertEqual(p.exitcode, 0)
self.assertEqual(q.get_nowait(), "done")
close_queue(q)


#
# Mixins
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :exc:`RuntimeError` when using a not-started :class:`threading.Thread` after calling :func:`os.fork`
6 changes: 6 additions & 0 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,12 @@ _PyThread_AfterFork(struct _pythread_runtime_state *state)
continue;
}

// Keep handles for threads that have not been started yet. They are
// safe to start in the child process.
if (handle->state == THREAD_HANDLE_NOT_STARTED){
continue;
}

// Mark all threads as done. Any attempts to join or detach the
// underlying OS thread (if any) could crash. We are the only thread;
// it's safe to set this non-atomically.
Expand Down
Loading