fix(sessions): fold the refresh begin into the first batch commit - #3080
devin-ai-integration[bot] wants to merge 2 commits into
Conversation
A streamed message committed four times inside temporal refresh: the begin's operation row, the first projection batch, the pending receipt, and activation. The begin now replays inside a rolled-back write transaction to produce the same recovery a durable begin would have left, the projector builds the first batch against it, and one commit writes the begin and the batch together. A replayed begin that no longer matches the projected batch commits alone and durable recovery resumes the operation next pass, the state a crash between the two commits already left behind. The shared cursor key is provisioned in its own transaction first so both replays read the same key, and the operation's created_at stays the plan's accepted_at so progress rows written between plan and commit stay ordered. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
A pending reset deletes the base rows the first batch would project from, so a batch projected before the begin commits can never match the replayed begin's post-reset state; the fold always fell back to BeganOnly and cost an extra pass. Plan now checks for a pending reset inside the first transaction and commits the begin when one exists, so durable recovery picks the operation up in the same pass and projects the post-reset base. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Correct — a pending reset deletes the base rows inside the begin's transaction, so a batch projected before that commit can never match the replayed begin's post-reset state; the fold deterministically fell back to Fixed in |
Summary
Fixes #2939
Motivation
Issue #2939 reports a streamed message commits 9 times / 246 WAL frames for 193 pages. After earlier folds (capture span, drain convergence, receipt-into-activation), refresh still committed four times per message: begin operation row, first projection batch, pending relation receipt, and activation. The begin→batch fold is the remaining commit that collapses without redesigning the write-ahead receipt protocol; this PR lands the state-machine change #3017 identified.
Changes
crates/tracedecay-session-temporal-store/src/refresh.rs:plan_session_refresh_begin_resultreplays the begin in a rolled-back write transaction and returns aSessionRefreshBeginPlanV1::Preparedrecovery;commit_session_refresh_begin_batch_resultreplays the same begin in the committing transaction and persists the first batch only when the replayed binding still matches it. On divergence or batch refusal the begin commits alone (BeganOnly), leaving the durable running operation a crash between the two old commits would already have left. The shared cursor key is provisioned in its own transaction before planning (a rolled-back mint would desynchronize the two replays; once a key exists the provision commit appends no WAL frames), andSessionRefreshRecoveryV1now carriesaccepted_atso the operation'screated_atstays ordered beforeprogress.recorded_at.crates/tracedecay-session-temporal-store/src/handle.rs(+tracedecay-global-db,test_registered_impls.rs):SessionTemporalWriteTxn::rollbackso the plan transaction can be abandoned without committing.crates/tracedecay-session-runtime/src/session_temporal_refresh_scheduler/worker.rs:PreparedSessionRefreshcarries the planned recovery through the pass;apply_prepared_refresh_effectfolds the projection effect throughcommit_session_refresh_begin_batchand keeps Fail/Deferred/error accounting identical to the durable paths.session_store_read_costboundary expectation(4,3,4) → (4,3,3);temporal_refreshsuite tail asserts the folded recovery pass.Test plan
bash scripts/require-exact-test.sh cargo test -p tracedecay-session-runtime --features test-helpers --test session_store_read_cost streamed_message_commits_once_per_durability_boundary -- --exact— 1 passed (red before: measured(4,3,4), now(4,3,3))cargo test -p tracedecay --features test-helpers --test session_suite session_runtime::temporal_refresh— 24 passedcargo test -p tracedecay-session-temporal-store --lib— 152 passedcargo test -p tracedecay-session-runtime --lib— 129 passedcargo clippy -p tracedecay-session-temporal-store|tracedecay-global-db|tracedecay-session-runtime|tracedecay --all-targets -- -D warnings— cleancargo fmt --all -- --check— cleanChecklist
.envfiles includedLink to Devin session: https://app.devin.ai/sessions/0bf7d9f457e7462784c8852594601226
Open in Devin Desktop: https://app.devin.ai/desktop/session/0bf7d9f457e7462784c8852594601226?variant=devin
Requested by: @ScriptedAlchemy