mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(deadline): prefer a ready result over a same-window deadline/cancel
run_with_deadline checked the deadline/cancel before reading the result queue, so a call that completed in the same scheduling window could be reported as a spurious timeout. Drain the queue first. Also from review: - test_validate_regex_pattern stubs run_with_deadline, so the probe regex never runs — use a benign pattern instead of a real backtracking literal (the literal tripped a ReDoS scanner). - output_guard_judge docstring: reference IntentJudge._parse_verdict instead of brittle judge.py line numbers. The _runner BaseException catch is intentional and kept: it relays (not swallows) whatever fn() raises to the caller via the queue; narrowing to Exception would let a BaseException escape the worker so the caller never gets a value, degrading the no-hang guarantee.
This commit is contained in:
@@ -27,7 +27,9 @@ def test_catastrophic_backtracking_returns_message(monkeypatch) -> None:
|
||||
raise DeadlineExceededError
|
||||
|
||||
monkeypatch.setattr("turnstone.console.server.run_with_deadline", _deadline)
|
||||
assert _validate_regex_pattern(r"(a+)+$") == "Regex appears to have catastrophic backtracking"
|
||||
# The pattern is arbitrary — run_with_deadline is stubbed to raise, so the
|
||||
# probe never runs; a real backtracking literal here would only trip CodeQL.
|
||||
assert _validate_regex_pattern(r"\w+") == "Regex appears to have catastrophic backtracking"
|
||||
|
||||
|
||||
def test_probe_error_returns_generic_message(monkeypatch) -> None:
|
||||
|
||||
@@ -68,6 +68,18 @@ def run_with_deadline(
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
# Prefer a result that has already arrived over a deadline or cancel
|
||||
# firing in the same scheduling window — otherwise a completed call
|
||||
# could be reported as a spurious timeout/cancel under jitter.
|
||||
try:
|
||||
ok, payload = box.get_nowait()
|
||||
except queue.Empty:
|
||||
pass
|
||||
else:
|
||||
if ok:
|
||||
return payload # type: ignore[return-value] # ok=True ⇒ payload is _T
|
||||
raise payload # type: ignore[misc] # ok=False ⇒ payload is the raised exc
|
||||
|
||||
if cancel_event is not None and cancel_event.is_set():
|
||||
raise DeadlineCancelledError
|
||||
remaining = deadline - time.monotonic()
|
||||
@@ -78,5 +90,5 @@ def run_with_deadline(
|
||||
except queue.Empty:
|
||||
continue
|
||||
if ok:
|
||||
return payload # type: ignore[return-value] # ok=True ⇒ payload is _T
|
||||
raise payload # type: ignore[misc] # ok=False ⇒ payload is the raised exc
|
||||
return payload # type: ignore[return-value]
|
||||
raise payload # type: ignore[misc]
|
||||
|
||||
@@ -12,7 +12,7 @@ Design:
|
||||
a static tool result doesn't benefit from multi-turn — the text is
|
||||
already in hand.
|
||||
- JSON-in-content verdict. 4-strategy parser inlined from
|
||||
:class:`IntentJudge` (``judge.py:1603-1659``).
|
||||
:meth:`IntentJudge._parse_verdict`.
|
||||
- Wall-clock deadline via :func:`turnstone.core.deadline.run_with_deadline`,
|
||||
which runs the call on a *daemon* worker and polls the cancel event each
|
||||
second. A timeout or cancel abandons the call rather than waiting it out,
|
||||
|
||||
Reference in New Issue
Block a user