mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(compaction): review round 9 — drain-exit ownership, missed-edge settle, pre-turn hook guard
Three point-guards from the ceiling round (no primitive took a hit;
correctness yield halved at identical review sensitivity):
- The drain's clean-exit wake moved OUT of the function-level try: it
runs after the drain has already retired its slot, so a raise out of
the wake (the dispatcher re-raises Thread.start failures) could reach
the last-resort handler and clear a slot this thread no longer owned —
nulling a successor drain's live registration and letting two drains
service one list. The wake now runs post-try under its own guard
(mirroring _retry_pending_wake), only on the clean-exit path, and the
last-resort slot-clear is identity-guarded like every sibling exit
seam. The except arm needed a function-local threading import: the
module-top import is TYPE_CHECKING-only, so the guard would have
NameErrored inside the handler with strict mypy fully green.
- The shared settle helper promotes a non-deferred chip that binds onto
an already-idle pane: its only sweep fired mid-POST (unbound then) and
no message_dispatched ever comes for non-deferred sends, so the chip
stayed a permanently retractable "queued" bubble for a delivered
message. Keyed on post-bind chip state (also catching a raced folded
settle bind just reconciled) and skipping dismiss-in-flight chips —
the sweep's own aria-busy discipline. Pinned behaviorally: the helper
now executes under node (a 4-row missed-edge matrix), possible since
the consumer-less window bridge is gone.
- _claim_generation's on_generation_claimed emission is call-guarded:
it sits on send()'s pre-turn path, before the user turn is appended
and before the fatal handler's coverage, so a raising override
degrades to a lost latch-break instead of silently dropping every
user message on that session.
Cleanups: /command's transport catch and status-less non-2xx bodies are
loud now (threading {ok, status} through the parse — deliberately no
throw-on-!ok pre-gate, since the busy and error arms ride 409/503);
PENDING_SENDS_MAX lives in workstream.py and ChatSession._QUEUE_MAX
aliases it (one backpressure bound, structurally incapable of
diverging); the send handler's not-ok arm uses _queue_full_response();
the dead window.createQueueController bridge is deleted and the file
header's consumer map corrected.
This commit is contained in:
@@ -2170,6 +2170,34 @@ class TestPreHookUICompat:
|
||||
)
|
||||
|
||||
|
||||
class TestClaimGenerationHookGuard:
|
||||
"""_claim_generation's on_generation_claimed emission sits on send()'s
|
||||
PRE-turn path — before the user turn is appended, before
|
||||
_record_fatal_error's coverage — so a raising override must degrade
|
||||
to a lost latch-break, never to a silently dropped user message."""
|
||||
|
||||
def test_raising_claim_hook_does_not_abort_the_claim(self, session):
|
||||
calls: list[int] = []
|
||||
|
||||
def _boom(gen: int) -> None:
|
||||
calls.append(gen)
|
||||
raise RuntimeError("broadcast backend down")
|
||||
|
||||
session.ui = SimpleNamespace(
|
||||
on_thinking_start=lambda: None,
|
||||
on_thinking_stop=lambda: None,
|
||||
on_error=lambda _m: None,
|
||||
on_generation_claimed=_boom,
|
||||
)
|
||||
before = session._generation
|
||||
claimed = session._claim_generation()
|
||||
assert claimed == before + 1 # claim-state writes stayed infallible
|
||||
assert session._generation == claimed
|
||||
assert calls == [claimed] # the hook WAS attempted, then contained
|
||||
# And again — every claim survives, not just the first.
|
||||
assert session._claim_generation() == claimed + 1
|
||||
|
||||
|
||||
class TestCompactionNoticeStamp:
|
||||
"""_compaction_event is the single display-policy site: failed ends
|
||||
carry ``notice`` — renderers show the message iff it is true, instead
|
||||
|
||||
Reference in New Issue
Block a user