You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(engine): retire a terminal group's deferred-peel rows so they stop charging the account budget (#1784)
* fix(engine): retire a terminal group's deferred-peel rows so they stop charging the account budget
When this device's copy of a group became terminal, its PeelDeferred rows
were never retired. The sweep that releases such rows is reached only
through prepare_convergence_input_advance, which refuses a removed copy
outright and a disbanded one via EpochState::Disbanded, while
ensure_peel_deferred_usage_initialized re-counts every group's rows into
the deferred-peel account budget on each open. Terminal groups therefore
held their per-group row slots and their share of the account byte
budget forever; a disbanded group also showed pending deferred work in
conformance snapshots beside zeroed unresolved inputs.
Retire the rows silently at every marker site: inside the disband settle
transaction beside delete_deferred_peel_generation (its idempotent
re-entry never re-runs the body, so a purge outside it could be skipped
forever after a crash), at realize_self_eviction, at the commit-apply
self-removed arm (realize_self_eviction early-returns once removed is
written, so that seam owns its own purge), at the convergence-reorg
marker, and once more in the terminal gate as crash-window recovery. The
durable flip enumerates payload-free metadata and runs inside the
transaction; the in-memory budget release runs after commit, safe because
the counters are derived state rebuilt from durable rows on open. Rows go
to Failed like every other retired deferred row, and nothing on this path
emits TransportObjectResourceRefused.
Consolidate the engine's hand-rolled terminal predicates onto
Group::is_terminal where the predicate is genuinely either kind of
terminal; the send gates stay on removed because the tombstone gate above
each already refuses a disbanded copy and their error names removal.
Tests drive every behavior through ingest and advance_convergence. The
regression guard for a row terminalized during a sweep is re-pointed at a
live-group shape so it enumerates rows again.
* fix(engine): flip a terminal group's deferred rows in one transaction
Outside a transaction each update_message_state autocommitted, so a
storage error mid-loop left earlier rows durably Failed with their budget
slot still charged and no transition audit, and a retry that enumerates
PeelDeferred only never saw them. Run the durable half of the retire in
one transaction; a failed retire now leaves every row PeelDeferred for
the next pass. Nesting is safe: the SQLite backend reuses a same-thread
outer transaction, so the disband settle is unaffected.
Pinned with a FaultStorage test that fails the second Failed-state write
during a removal and asserts both rows stay deferred, then retires both
on the next advance.
0 commit comments