feat(outbox): collect exit verb on BatchOp - #97
Draft
bodymindarts wants to merge 4 commits into
Draft
bodymindarts wants to merge 4 commits into
bodymindarts wants to merge 4 commits into
Conversation
bodymindarts
marked this pull request as draft
August 3, 2026 13:34
nicolasburtey
force-pushed
the
feat/batch-op-collect
branch
from
August 4, 2026 17:51
7f0af16 to
38ac4ce
Compare
Newer clippy flags the RecordingHook chunks field (type_complexity) and a MutexGuard held across the final await in hook_sees_persisted_events_pre_commit (await_holding_lock). Introduce a RecordedChunks alias and scope the guard so it drops before the await. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Handlers entering via consume_in_batch could only exit with commit or defer — an event needing both direct op work and a contribution to the Batch accumulator had to give up one or the other. BatchOp::collect_with (with Vec/HashMap collect sugar, mirroring EventCtx) closes that gap: a defer-like exit that also contributes an item, applied by the handler's flush inside the same batch transaction when the batch lands. BatchOp becomes generic over the accumulator (BatchOp<'inv, B = ()>); the defaulted param keeps existing BatchOp<'_> mentions compiling. The runner is untouched: Outcome::Collect already lands like Defer, and flush_batch already handles op+items batches. The exit only bumps tracker.collected — consume_in_batch already counted the event, so bumping events_in_op again would double-count it against max_batch_size (pinned by the new test's single-flush assertion). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… test ephemeral_never_interrupts_an_open_batch published its ephemeral after a fixed 150ms sleep, assuming the job was already mid-drain. On a slow CI runner the job hadn't started consuming yet, so the ephemeral was handled before the batch opened and snapshotted zero rows (CI failure: left 0, right 5) — a scheduling artifact, not a violation of the invariant under test. Record persistent deliveries and publish the ephemeral only once the second event is being handled: the first event has then provably deferred into an open op, so the mid-batch arrival is guaranteed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sibling regression to ephemeral_never_interrupts_an_open_batch for the other batch-open flag: a slow collect-only handler's batch is pending purely via the accumulator (collected > 0, no op ever opens until the flush). Same evidence-gated timing — the ephemeral is published only once the second delivery proves the first event already collected — and the same invariant: the ephemeral runs at the batch boundary, after all N items flushed in one batch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nicolasburtey
force-pushed
the
feat/batch-op-collect
branch
from
August 5, 2026 17:15
38ac4ce to
9f62ca9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
collecttoBatchOpas a sibling exit verb todefer()/commit(), completing the verb set introduced in #95.Handlers entering via
consume_in_batchcould only exit withcommitordefer— an event needing both direct op work now and a contribution to theBatchaccumulator had to give up one or the other.BatchOp::collect_with(plusVec/HashMapcollectsugar, mirroringEventCtx) closes that gap: a defer-like exit that also contributes an item, applied by the handler'sflushinside the same batch transaction when the batch lands.Design notes
Outcome::Collectalready lands likeDefer, andflush_batchalready handles op+items batches — the whole change lives inctx.rs.tracker.collected;consume_in_batchalready counted the event towardmax_batch_size. The new test pins this: withmax_batch_size == N, a double count would split the burst into two flushes.IsolatedOp— deliberately. A collected item lands with a future batch flush, not with the isolated op's commit, so a collect exit there would silently violate the isolation contract.BatchOp<'inv>→BatchOp<'inv, B = ()>. The defaulted param keeps existingBatchOp<'_>mentions compiling; only code namingBatchOpexplicitly inside a non-unit-Batchhandler (a combination made possible yesterday in feat(outbox)!: collect/flush statement batching, two-stream runner, sealed op guards #95) could notice. Effectively additive — no!.Also includes a standalone commit fixing two pre-existing clippy lints in
tests/post_persist_hook.rs(type_complexity,await_holding_lock) that were blockingnix flake check.Testing
batch_op_collect_contributes_item_and_defers: each event writes a row directly into the shared batch op and collects an offset item; asserts both land (one batch, one flush, one checkpoint) and that events count once towardmax_batch_size.nix flake check(fmt, clippy-D warnings, deny, tests) passes; all 31 tests in the two touched suites green.🤖 Generated with Claude Code