diff --git a/src/trio/_core/_thread_cache.py b/src/trio/_core/_thread_cache.py index f48aa8eab..3d044d6de 100644 --- a/src/trio/_core/_thread_cache.py +++ b/src/trio/_core/_thread_cache.py @@ -188,6 +188,13 @@ def _handle_job(self) -> None: if set_os_thread_name: set_os_thread_name(self._thread.ident, self._default_name) + # Without this, this thread may get rescheduled after `deliver` + # and only return after the kernel gets around to it. That's an + # arbitrary amount of time during which we keep anything + # referred to in the function alive, like contextvars. That's + # surprising! + del fn + # Tell the cache that we're available to be assigned a new # job. We do this *before* calling 'deliver', so that if # 'deliver' triggers a new job, it can be assigned to us diff --git a/src/trio/_tests/test_threads.py b/src/trio/_tests/test_threads.py index 532a20d36..f349850ff 100644 --- a/src/trio/_tests/test_threads.py +++ b/src/trio/_tests/test_threads.py @@ -1,7 +1,6 @@ from __future__ import annotations import contextvars -import gc import queue as stdlib_queue import re import sys @@ -29,7 +28,7 @@ sleep_forever, ) from .._core._tests.test_ki import ki_self -from .._core._tests.tutil import slow +from .._core._tests.tutil import gc_collect_harder, slow from .._threads import ( active_thread_count, current_default_thread_limiter, @@ -705,7 +704,8 @@ def sync_fn() -> None: cvar.set(Foo()) del contextval - gc.collect() + if sys.implementation.name == "pypy": + gc_collect_harder() assert ref() is None