镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

A retrying transaction replays rendered frames: RetryTransaction and the [AutoDatabase] generator go - #3339

Open
mgravell wants to merge 1 commit into
marc/v4-database-cleanupfrom
marc/v4-tx-retry
Open

mgravell wants to merge 1 commit into
marc/v4-database-cleanupfrom
marc/v4-tx-retry

Conversation

@mgravell

@mgravell mgravell commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Second of the two-PR series, stacked on #3338 — review just this diff; it retargets to v4 when #3338 lands (I'll rebase onto the squash-merged v4 at that point rather than letting the stale base replay). Net: +760 / −1,804 across 23 files.

The idea

A transaction is RESP blobs paired to results, the same as everything else — so it retries the same way everything else does: by replaying rendered frames, not by capturing and replaying method calls.

RespRetryTransactionExecutor records each queued command's rendered RespRequest plus a durable caller proxy at queue time, and conditions semantically with per-attempt verdict buffers. Each attempt builds a fresh one-shot RespTransactionExecutor and replays the frames into it; on a clean commit the attempt's results are forwarded into the durable proxies, on a retryable fault the attempt is discarded whole and replayed. It shares the database's RetryController, so budgets, failover tracking and watch-conflict policy are the ones a plain command sees; the failover token is captured before the first attempt. ITransactionExecutor is the seam RedisTransaction consumes, so plain and retrying transactions are the same type over different executors.

Fresh attempt operations per replay are what dodge the concrete blocker that kept the capture-replay wrapper alive: enrolled pooled operations are faulted directly by connection teardown, so re-enrolling one races pool recycling.

What goes

With its last consumer gone, the capture-and-replay tier is deleted whole: RetryTransaction, AutoDatabase.cs, CapturedArgs.cs, the AutoDatabaseGenerator, diagnostic SER352 (+ its docs page), and IInternalTransaction. Nothing on IDatabase is source-generated any more; DatabaseSurfaceTests now pins that nothing creeps back.

Deliberate behavior changes

  • A retrying transaction now carries asyncState (previously refused with an exception).
  • Its context works and queues into the recorded unit (previously threw NotImplementedException — the wrapper recorded method calls, so frames had nowhere to go; the frame recorder takes them natively). Reuse after execute is still refused.
  • Fire-and-forget parity is now real rather than claimed: both transaction executors hand every declined reply the one shared completed-default task, and TaskBridge passes a completed fire-and-forget task through untouched instead of rewrapping it — strictly fewer allocations for the plain transaction too.

Verification

Full net10.0 suite green (7,628 tests) against the docker topology, including the retry acceptance suite (WithRetry_Transaction_*: LOADING replay, watch-conflict budgets, group failover mid-transaction) and the transaction/queued-result behavioral pins. Full traversal build across all TFMs with analyzers: 0 warnings.

… RetryTransaction and the [AutoDatabase] generator go

RespRetryTransactionExecutor records each queued command's rendered RespRequest plus a durable
caller proxy, then replays the frames into a fresh one-shot RespTransactionExecutor per attempt,
sharing the database's RetryController. Fresh attempt operations per replay are what dodge the
real blocker: enrolled pooled operations are faulted directly by connection teardown, so
re-enrolling one races pool recycling.

With its last consumer gone, the capture-and-replay tier is deleted whole: RetryTransaction,
AutoDatabase.cs, CapturedArgs.cs, the AutoDatabaseGenerator, SER352, and IInternalTransaction.
Nothing on IDatabase is source-generated any more.

Deliberate behavior changes: a retrying transaction now carries asyncState (previously refused)
and exposes a working context that queues into the recorded unit (previously threw). Reuse after
execute is still refused. Fire-and-forget parity is now real rather than claimed: both transaction
executors hand every declined reply the one shared completed-default task, and TaskBridge passes a
completed fire-and-forget task through untouched instead of rewrapping it.

This branch has not been deployed

No deployments
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