Cosmos: flow transactional batch failures through the execution strategy so they can be retried#38597
Open
AndriySvyryd with Copilot wants to merge 12 commits into
Open
Cosmos: flow transactional batch failures through the execution strategy so they can be retried#38597AndriySvyryd with Copilot wants to merge 12 commits into
AndriySvyryd with Copilot wants to merge 12 commits into
Conversation
…angesAsync Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix retry mechanism for transactional batch failures
Cosmos: flow transactional batch failures through the execution strategy so they can be retried
Jul 9, 2026
There was a problem hiding this comment.
Pull request overview
This PR fixes Cosmos transactional batch failures bypassing the execution strategy retry pipeline by throwing on batch failure (instead of returning a “failed result”) and moving the execution-strategy scope to wrap the full SaveChanges batch sequence, allowing transient failures to reach ShouldRetryOn and be retried.
Changes:
- Replace the transactional batch “result” return pattern with a
CosmosTransactionalBatchExceptionthrown fromProcessBatchResponse. - Change
ICosmosClientWrapper.ExecuteTransactionalBatchAsyncto returnTaskand execute once (no per-batch execution strategy wrapping in the client). - Wrap the full set of transactional batches in a single execution strategy invocation inside
CosmosDatabaseWrapper, with retry skipping of already-committed operations, and add a functional test verifying the exception reaches the execution strategy.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/EFCore.Cosmos.FunctionalTests/CosmosTransactionalBatchTest.cs | Adds a functional test asserting transactional batch failures flow through the execution strategy and nothing is committed. |
| src/EFCore.Cosmos/Storage/Internal/ICosmosClientWrapper.cs | Updates the transactional batch API to return Task rather than a result object. |
| src/EFCore.Cosmos/Storage/Internal/CosmosTransactionalBatchException.cs | Introduces an internal exception type carrying errored entries and the underlying CosmosException. |
| src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs | Moves execution strategy retry scope to encompass the entire batch set and adds committed-operation skipping across retries. |
| src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs | Executes transactional batches once and throws CosmosTransactionalBatchException on failure (instead of returning a failure result). |
…e transactional batch Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…fected; add test Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…currencyException for suppression Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
AndriySvyryd
marked this pull request as ready for review
July 11, 2026 03:43
…nalBatchResponse Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…specific exceptions in fallback Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
AndriySvyryd
approved these changes
Jul 14, 2026
… RuntimeFeature.IsDynamicCodeSupported for NativeAOT Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…NativeAOT-compatible ctor lookup Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…atically known Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
cincuranet
reviewed
Jul 17, 2026
| .GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance) | ||
| .FirstOrDefault(static c => | ||
| { | ||
| var parms = c.GetParameters(); |
Contributor
There was a problem hiding this comment.
nit: I personally don't like the parms identifier.
cincuranet
reviewed
Jul 17, 2026
Comment on lines
+60
to
+62
| && parms[3].ParameterType.FullName == "Microsoft.Azure.Cosmos.Headers" | ||
| && parms[4].ParameterType.Name == "ITrace" | ||
| && parms[5].ParameterType.Name == "Error" |
Contributor
There was a problem hiding this comment.
Should the FullName be used for all these 3? You know consistency is key.
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.
Fixes #38044
Cosmos transactional batch failures returned a result object from
ProcessBatchResponserather than throwing, so the failure surfaced as aDbUpdateExceptionoutside any execution-strategy scope and could never be retried by anExecutionStrategy(ShouldRetryOnwas never called).Changes
CosmosClientWrapper.ProcessBatchResponsenow computes the errored entries (those whose per-item status code matches the batch-level error code), wraps theCosmosExceptioninto aDbUpdateConcurrencyException(PreconditionFailed) orDbUpdateException(Conflict / other), and throws it directly.CosmosTransactionalBatchResultandCosmosTransactionalBatchExceptionare removed.ExecuteTransactionalBatchAsyncno longer wraps itself in the execution strategy; it runs once and returnsTask(interface updated).SaveChangesAsync—CosmosDatabaseWrappertakes an injectedIExecutionStrategyand wraps the whole set of batches in a singleExecuteAsync. ABatchExecutionState.CommittedOperationscounter lets a retry skip already-committed operations while preserving the original ordering of single-entry saves interleaved with transactional batches. The execution strategy unwrapsDbUpdateExceptionviaExecutionStrategy.CallOnWrappedExceptionto reach the innerCosmosExceptionbefore callingShouldRetryOn, so transient failures are correctly identified and retried.DbUpdateConcurrencyExceptionfrom a batch is suppressed viaOptimisticConcurrencyExceptionAsync,RowsAffectedis not incremented (matchingSaveAsyncsingle-entry behavior), whileCommittedOperationsstill advances so a subsequent retry skips the already-processed operation.Because a transactional batch is atomic, a failed batch commits nothing and is retried in place; deterministic transaction serialization across attempts makes index-based skipping of committed operations valid.
Result
The repro from the issue now behaves as expected — a transient batch failure reaches the strategy and is retried:
Two functional tests verify the behavior:
SaveChanges_transactional_batch_failure_flows_through_execution_strategy— a conflicting batch reachesShouldRetryOnand yieldsRetryLimitExceededException, with nothing committed.SaveChanges_suppressed_concurrency_exception_in_transactional_batch_reports_zero_rows_affected— a stale-ETag batch update suppressed by an interceptor returns 0 rows affected and leaves the store unchanged.