Skip to content

Commit b92131c

Browse files
authored
Merge pull request #3200 from A5rocks/clear-after-fork
Clear worker thread cache after forking
2 parents 09daf03 + 04845c8 commit b92131c

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

newsfragments/2764.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Clear Trio's cache of worker threads upon `os.fork`.

src/trio/_core/_tests/test_thread_cache.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import os
34
import threading
45
import time
56
from contextlib import contextmanager
@@ -195,3 +196,47 @@ def deliver(_: object) -> NoReturn:
195196
err = capfd.readouterr().err
196197
assert "don't do this" in err
197198
assert "delivering result" in err
199+
200+
201+
@pytest.mark.skipif(not hasattr(os, "fork"), reason="os.fork isn't supported")
202+
def test_clear_thread_cache_after_fork() -> None:
203+
assert hasattr(os, "fork")
204+
205+
def foo() -> None:
206+
pass
207+
208+
# ensure the thread cache exists
209+
done = threading.Event()
210+
start_thread_soon(foo, lambda _: done.set())
211+
done.wait()
212+
213+
child_pid = os.fork()
214+
215+
# try using it
216+
done = threading.Event()
217+
start_thread_soon(foo, lambda _: done.set())
218+
done.wait()
219+
220+
if child_pid != 0:
221+
# if this test fails, this will hang, triggering a timeout.
222+
os.waitpid(child_pid, 0)
223+
else:
224+
# this is necessary because os._exit doesn't unwind the stack,
225+
# so coverage doesn't get to automatically stop and save
226+
# coverage information.
227+
try:
228+
import coverage
229+
230+
cov = coverage.Coverage.current()
231+
# the following pragmas are necessary because if coverage:
232+
# - isn't running, then it can't record the branch not
233+
# taken
234+
# - isn't installed, then it can't record the ImportError
235+
236+
if cov: # pragma: no branch
237+
cov.stop()
238+
cov.save()
239+
except ImportError: # pragma: no cover
240+
pass
241+
242+
os._exit(0) # pragma: no cover # coverage was stopped above.

src/trio/_core/_thread_cache.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ctypes
44
import ctypes.util
5+
import os
56
import sys
67
import traceback
78
from functools import partial
@@ -300,3 +301,15 @@ def start_thread_soon(
300301
301302
"""
302303
THREAD_CACHE.start_thread_soon(fn, deliver, name)
304+
305+
306+
def clear_worker_threads() -> None:
307+
# This is OK because the child process does not actually have any
308+
# worker threads. Additionally, while WorkerThread keeps a strong
309+
# reference and so would get affected, the only place those are
310+
# stored is here.
311+
THREAD_CACHE._idle_workers.clear()
312+
313+
314+
if hasattr(os, "register_at_fork"):
315+
os.register_at_fork(after_in_child=clear_worker_threads)

0 commit comments

Comments
 (0)