test(tools): pin daemon-pool worker arg shape for both stdlib contracts
Replace the cherry-picked 3.14-shape test with two interpreter-agnostic invariants. The original test did `del pool._initializer` (raises on 3.14, where the attribute never exists) and drove `_WorkItem.run()` with no `ctx` (TypeError on 3.14), so it could only ever pass on 3.11-3.13 — the exact interpreters where the bug does not occur. The fake worker now records the args tuple and resolves the work item's future directly, so the tests assert only on the shape the executor picks: `(ref, ctx, queue)` when `_create_worker_context` exists (3.14+), `(ref, queue, initializer, initargs)` when the legacy fields do (3.11-3.13). Both run green on 3.11 and 3.14; the 3.14-shape test is red on main under either interpreter (`AttributeError: ... no attribute '_initializer'`). Refs #58596, #111813.
This commit is contained in:
@@ -94,26 +94,63 @@ 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 _capture_worker_args(monkeypatch, pool):
|
||||
"""Swap the stdlib worker for one that records its args and resolves one item.
|
||||
|
||||
def worker_with_context(_executor_ref, worker_context, work_queue):
|
||||
seen_contexts.append(worker_context)
|
||||
work_queue.get().run()
|
||||
The stdlib ``_worker`` signature differs between interpreters, so the fake
|
||||
accepts anything and completes the work item's future directly — the test
|
||||
then runs on 3.11 and 3.14 alike and asserts only on the arg shape chosen.
|
||||
"""
|
||||
seen = []
|
||||
|
||||
def fake_worker(*args):
|
||||
seen.append(args)
|
||||
pool._work_queue.get().future.set_result("done")
|
||||
|
||||
monkeypatch.setattr(daemon_pool, "_worker", fake_worker)
|
||||
return seen
|
||||
|
||||
|
||||
def test_worker_gets_context_when_executor_builds_worker_contexts(monkeypatch):
|
||||
"""3.14+ shape (#58596, #111813): the executor exposes ``_create_worker_context``
|
||||
and no ``_initializer``/``_initargs``; the worker must receive
|
||||
``(executor_ref, ctx, work_queue)`` — reading the legacy fields raised
|
||||
``AttributeError`` on every pool spawn."""
|
||||
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
|
||||
monkeypatch.setattr(pool, "_create_worker_context", lambda: "worker-context", raising=False)
|
||||
monkeypatch.delattr(pool, "_initializer", raising=False)
|
||||
monkeypatch.delattr(pool, "_initargs", raising=False)
|
||||
seen = _capture_worker_args(monkeypatch, pool)
|
||||
try:
|
||||
assert pool.submit(lambda: "done").result(timeout=10) == "done"
|
||||
assert seen_contexts == ["worker-context"]
|
||||
assert pool.submit(lambda: None).result(timeout=10) == "done"
|
||||
finally:
|
||||
pool.shutdown(wait=True)
|
||||
((executor_ref, ctx, work_queue),) = seen
|
||||
assert executor_ref() is pool
|
||||
assert ctx == "worker-context"
|
||||
assert work_queue is pool._work_queue
|
||||
|
||||
|
||||
def test_worker_gets_initializer_when_executor_stores_initializer_fields(monkeypatch):
|
||||
"""3.11–3.13 shape: no ``_create_worker_context``; the worker must receive
|
||||
``(executor_ref, work_queue, initializer, initargs)``."""
|
||||
|
||||
def init(*_):
|
||||
return None
|
||||
|
||||
pool = DaemonThreadPoolExecutor(max_workers=1)
|
||||
monkeypatch.delattr(pool, "_create_worker_context", raising=False)
|
||||
monkeypatch.setattr(pool, "_initializer", init, raising=False)
|
||||
monkeypatch.setattr(pool, "_initargs", (1, 2), raising=False)
|
||||
seen = _capture_worker_args(monkeypatch, pool)
|
||||
try:
|
||||
assert pool.submit(lambda: None).result(timeout=10) == "done"
|
||||
finally:
|
||||
pool.shutdown(wait=True)
|
||||
((executor_ref, work_queue, initializer, initargs),) = seen
|
||||
assert executor_ref() is pool
|
||||
assert work_queue is pool._work_queue
|
||||
assert (initializer, initargs) == (init, (1, 2))
|
||||
|
||||
|
||||
def _repo_root():
|
||||
|
||||
Reference in New Issue
Block a user