Skip to content

Marc/aa2 retry tran - #3139

Merged
mgravell merged 4 commits into
marc/aa2from
marc/aa2-retry-tran
Jul 28, 2026
Merged

Marc/aa2 retry tran#3139
mgravell merged 4 commits into
marc/aa2from
marc/aa2-retry-tran

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

Implement transaction retries

Checklist

  • I fully and freely contribute this code in accordance with the project license (and am legally able to do so)
  • I take responsibility for this contribution's quality and correctness, including any portions produced with AI assistance (see CONTRIBUTING.md).

mgravell added 4 commits July 28, 2026 13:32
Introduces a retryable transaction, obtained via WithRetry(...).CreateTransaction().

Mechanism: RetryTransaction is an [AutoDatabase] recorder. The generator funnels each
builder call into (state, projection) - the same captured unit RetryDatabase replays -
which RetryTransaction records while handing the caller a durable, still-incomplete proxy
task. ExecuteAsync runs the retry loop: each attempt spins up a fresh one-shot inner
transaction against the underlying database (for multi-group this resolves the currently
active member, so a retry after failover lands on the new member), replays the recorded
ops and constraints onto it, and awaits it. On clean completion the per-attempt outcomes
are forwarded onto the durable proxies; on a transient fault the attempt is discarded and
replayed; on terminal failure the proxies are faulted.

The transaction's effective retry category is the most side-effecting of its operations
(WATCH constraints excluded); it is injected onto the EXEC flags so the resulting fault
carries it and the shared RetryPolicy/FaultContext gating applies exactly as for a single
command. Retry policy math is extracted into a shared RetryController used by both
RetryDatabase and RetryTransaction.

Surface (all [Experimental] SER007):
- new ITransactionAsync (async-only transaction; sibling of ITransaction, no shipped-API change)
- new IRetryDatabase with CreateTransaction; WithRetry now returns it
- RedisTransaction/KeyPrefixedTransaction also implement ITransactionAsync

Includes end-to-end tests: rides out a transient EXEC failure, respects the aggregate
category gate, and does not double-apply a discarded attempt.
Reworks the retryable-transaction surface so WithRetry keeps returning IDatabaseAsync
and RetryDatabase never has to implement IDatabase (which would drag in the sync
surface). CreateTransaction now lives on IDatabaseAsync, returning the async-only
ITransactionAsync; IDatabase.CreateTransaction refines the return type to ITransaction
via "new", with ITransaction : IBatch, ITransactionAsync.

To avoid any ambiguity, ITransaction re-declares AddCondition/ExecuteAsync(flags) with
"new" rather than relying on inheritance, so its surface is self-contained and its
shipped-API lines are unchanged.

- IDatabaseAsync gains CreateTransaction(): ITransactionAsync ([Experimental] SER007)
- IDatabase.CreateTransaction is now "new ITransaction ..." (signature unchanged)
- ITransaction : IBatch, ITransactionAsync (re-declares its two async members)
- the source generator skips CreateTransaction/CreateBatch (not replayable round-trips)
- explicit IDatabaseAsync.CreateTransaction bridges on RedisDatabase, MultiGroupDatabase,
  KeyPrefixedDatabase; KeyPrefixed<TInner> base throws (reimplemented by the database);
  RetryTransaction throws (nested)
- WithRetry returns IDatabaseAsync again; IRetryDatabase removed

All retry/transaction/batch/keyprefix tests pass; full Release build across all TFMs.
ITransaction (stable) derives from ITransactionAsync, so the type must be stable too;
the experimental gating stays on IDatabaseAsync.CreateTransaction (the new entry point).
Also drops the now-redundant explicit ITransactionAsync from RedisTransaction and
KeyPrefixedTransaction (implied by ITransaction : ITransactionAsync).
Now that CreateTransaction is on IDatabaseAsync, RetryTransaction only needs an
IDatabaseAsync source: it creates each attempt's transaction via source.CreateTransaction()
and uses only AddCondition/ExecuteAsync (both on ITransactionAsync). Removes the
IDatabase cast and its NotSupportedException path, so WithRetry(...).CreateTransaction()
works over any IDatabaseAsync.
@mgravell
mgravell marked this pull request as ready for review July 28, 2026 13:35
@mgravell
mgravell merged commit f209897 into marc/aa2 Jul 28, 2026
1 of 3 checks passed
@mgravell
mgravell deleted the marc/aa2-retry-tran branch July 28, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant