diff --git a/tests/tools/test_daemon_pool.py b/tests/tools/test_daemon_pool.py index 9370afb46f..0bab785095 100644 --- a/tests/tools/test_daemon_pool.py +++ b/tests/tools/test_daemon_pool.py @@ -14,6 +14,7 @@ import time from concurrent.futures.thread import _threads_queues +import tools.daemon_pool as daemon_pool from tools.daemon_pool import DaemonThreadPoolExecutor @@ -93,6 +94,28 @@ def test_submit_propagates_caller_contextvars(): pool.shutdown(wait=True) +def test_workers_support_worker_context_without_legacy_initializer_attrs(monkeypatch): + """Python 3.14 creates a worker context instead of storing initializer fields.""" + seen_contexts = [] + + def worker_with_context(_executor_ref, worker_context, work_queue): + seen_contexts.append(worker_context) + work_queue.get().run() + + pool = DaemonThreadPoolExecutor(max_workers=1) + monkeypatch.setattr(daemon_pool, "_worker", worker_with_context) + monkeypatch.setattr( + pool, "_create_worker_context", lambda: "worker-context", raising=False + ) + del pool._initializer + del pool._initargs + try: + assert pool.submit(lambda: "done").result(timeout=10) == "done" + assert seen_contexts == ["worker-context"] + finally: + pool.shutdown(wait=True) + + def _repo_root(): import pathlib diff --git a/tools/daemon_pool.py b/tools/daemon_pool.py index 5682a8f6ae..6e1cf88f88 100644 --- a/tools/daemon_pool.py +++ b/tools/daemon_pool.py @@ -36,7 +36,7 @@ class DaemonThreadPoolExecutor(ThreadPoolExecutor): return super().submit(_run_with_context, *args, **kwargs) def _adjust_thread_count(self) -> None: - # Mirrors CPython's implementation (3.8–3.13) with two changes: + # Mirrors CPython's implementation with two changes: # daemon=True and no _threads_queues registration. if self._idle_semaphore.acquire(timeout=0): return @@ -46,11 +46,27 @@ class DaemonThreadPoolExecutor(ThreadPoolExecutor): num_threads = len(self._threads) if num_threads < self._max_workers: thread_name = "%s_%d" % (self._thread_name_prefix or self, num_threads) + executor_ref = weakref.ref(self, weakref_cb) + if hasattr(self, "_create_worker_context"): + # Python 3.14 replaced _initializer/_initargs with a factory + # that supplies the worker's initializer context. + worker_args = ( + executor_ref, + self._create_worker_context(), + self._work_queue, + ) + else: + worker_args = ( + executor_ref, + self._work_queue, + self._initializer, + self._initargs, + ) # Carry the active profile into the review thread so MEMORY.md / skill review writes land in the # right profile (#54937). t = threading.Thread( name=thread_name, target=_worker, daemon=True, - args=(weakref.ref(self, weakref_cb), self._work_queue, self._initializer, self._initargs), + args=worker_args, ) t.start() self._threads.add(t)