Repository navigation
fix(idempotency): store observe results in the business transaction - #158
Merged
Merged
Conversation
Observe mode reserved and completed through the detached store, so a Completed record could outlive a business transaction whose commit failed. After the switch to Enforce, a retry replayed a success that never committed. Observe now reserves through the detached store, releases that reservation once the operation finishes, and writes the result through the attached store inside the business or ambient transaction. A failed commit rolls the result back with the business data, so the retry runs again. Release paths stay detached and Enforce is unchanged. The EF Core store guards a result write for a token it did not reserve with a savepoint (the EF savepoint API, or raw SAVEPOINT statements in an ambient PostgreSQL transaction), so a failed result write cannot abort the business transaction and lose its data on commit. The HTTP middleware applies the same flow.
…mmit Observe with an EF Core store, through the mediator inside the transaction and through the HTTP middleware under a unit of work: a failed commit leaves no record, so the retry under Enforce runs again and leaves one business row. Also covers a committed observe being replayed, a failing result write behind the savepoint, and ambient transactions at read committed and serializable.
… transaction A failing rollback to the result savepoint no longer replaces the database error that caused it, and a PostgreSQL connection that is not enlisted in the ambient transaction writes the result without a savepoint instead of failing. Cover the savepoint path on SQLite, and assert the aborted request explicitly.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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
Fixes a lost-work bug in
Observemode when idempotency runs inside the business transaction (MediatorTx → Idem → Lock, or HTTPUseIdempotencyinside a transaction) with the EF Core record store.Bug: Observe reserved and completed through the
Detachedstore, so theCompletedrecord was committed before the business transaction. If the business commit then failed, a retry after switching toEnforcereplayed a success that never committed.Fix:
Detached, releases that reservation detached after the operation, and writes the result through the attached store inside the business (or ambient) transaction. A failed commit rolls the record back too, so the retry runs the handler again.EfCoreIdempotencyRecordStore.CompleteAsyncguards a result for a token it did not reserve with a savepoint (EF savepoint API; rawSAVEPOINTfor a PostgreSQL ambient transaction), so a failing result write does not abort the business transaction.Enforceis unchanged. No public API change.Behavior notes (documented):
reserved, notreplay.READ COMMITTED.XACT_ABORT ONor aSNAPSHOTupdate conflict dooms the transaction, and a savepoint cannot recover it.ITransactionalIdempotencyRecordStoreimplementations: in Observe,CompleteAsyncis now called with a token whose reservation has already been released, so it must succeed when the entry is gone, per theIIdempotencyRecordStorecontract.Tests
Enlist=false.CSharpEssentials.Tests8134OpenApi.Tests86EntityFrameworkCore.Tests20Examples.Idempotency.EndToEnd14Examples.Idempotency.Redis28Follow-ups (next patch)
Offkill switch, and plan drift.