fix: fence self-heal worker registration against cancellation
(cherry picked from commit 0fb38b7b5c9453781353464ca5c48c7365359cf1)
This commit is contained in:
1 parent
6f14958bdc
commit
9b3951583d
3 files changed
+136
-18
No files matched your search
@@ -0,0 +1,95 @@
|
||||
"""Cancellation must fence both credential self-heal constructors.
|
||||
|
||||
Reuse the real worker/Stop fixture; only the external Agent and credential
|
||||
refresh are synthetic. No provider or tool operation is executed.
|
||||
"""
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import pytest
|
||||
|
||||
from api import config, streaming
|
||||
from tests.test_steer_worker_boundaries import worker_scene as worker_scene
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure_path", ["exception", "returned"])
|
||||
@pytest.mark.parametrize("cancellation", ["stop", "event-only", "detached-only", "none"])
|
||||
def test_self_heal_registration_obeys_retained_stop(worker_scene, monkeypatch, failure_path, cancellation):
|
||||
scene = worker_scene
|
||||
entered, release = threading.Event(), threading.Event()
|
||||
created, interrupted, finalized = [], [], []
|
||||
|
||||
def on_init():
|
||||
candidate = scene.agent
|
||||
created.append(candidate)
|
||||
original_interrupt = candidate.interrupt
|
||||
|
||||
def interrupt(reason):
|
||||
interrupted.append(candidate)
|
||||
return original_interrupt(reason)
|
||||
|
||||
candidate.interrupt = interrupt
|
||||
if len(created) == 2:
|
||||
entered.set()
|
||||
assert release.wait(8), "self-heal constructor barrier timed out"
|
||||
|
||||
def on_run():
|
||||
if len(created) == 1:
|
||||
if failure_path == "exception":
|
||||
raise RuntimeError("401 unauthorized")
|
||||
scene.result = {"error": "401 unauthorized", "messages": []}
|
||||
else:
|
||||
scene.result = None
|
||||
|
||||
scene.on_init, scene.on_run = on_init, on_run
|
||||
monkeypatch.setattr(streaming, "_attempt_credential_self_heal", lambda *args, **kwargs: {
|
||||
"provider": "openai", "api_key": "synthetic-self-heal-fixture",
|
||||
"base_url": "http://127.0.0.1:1/v1",
|
||||
})
|
||||
finalize = streaming._finalize_cancelled_turn
|
||||
|
||||
def observed_finalize(*args, **kwargs):
|
||||
assert scene.lock.owner != threading.get_ident(), "finalize held STREAMS_LOCK"
|
||||
finalized.append(True)
|
||||
return finalize(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(streaming, "_finalize_cancelled_turn", observed_finalize)
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
stop_future = None
|
||||
future = pool.submit(scene.run)
|
||||
try:
|
||||
assert entered.wait(8), "real worker did not enter credential self-healing"
|
||||
assert len(created) == 2
|
||||
retained = config.CANCEL_FLAGS["run"]
|
||||
if cancellation == "stop":
|
||||
# Stop can wait for the session lock after its atomic cancel
|
||||
# edge. Do not block constructor release on that later write.
|
||||
stop_future = pool.submit(streaming.cancel_stream, "run")
|
||||
assert retained.wait(5), "Stop did not publish cancellation"
|
||||
with config.STREAMS_LOCK:
|
||||
assert "run" not in config.STREAMS
|
||||
assert "run" not in config.CANCEL_FLAGS
|
||||
elif cancellation == "event-only":
|
||||
with config.STREAMS_LOCK:
|
||||
retained.set()
|
||||
config.CANCEL_FLAGS.pop("run")
|
||||
elif cancellation == "detached-only":
|
||||
with config.STREAMS_LOCK:
|
||||
config.STREAMS.pop("run")
|
||||
assert not retained.is_set()
|
||||
finally:
|
||||
release.set()
|
||||
future.result(timeout=12)
|
||||
if stop_future is not None:
|
||||
assert stop_future.result(timeout=12) is True
|
||||
|
||||
if cancellation == "none":
|
||||
assert scene.calls.count("run") == 2, "valid credential refresh must still retry"
|
||||
else:
|
||||
assert scene.calls.count("run") == 1, "cancelled self-heal executed the replacement Agent"
|
||||
cached = config.SESSION_AGENT_CACHE.get("original")
|
||||
assert not cached or cached[0] is not created[-1], "cancelled replacement entered the reusable cache"
|
||||
assert created[-1] in interrupted
|
||||
assert finalized
|
||||
assert "run" not in config.AGENT_INSTANCES
|
||||
assert "run" not in config.ACTIVE_RUNS
|
||||
Reference in new issue
Block a user