Skip to content

Fix Effect.tx spinning when its body recovers from Effect.txRetry - #10604

Open
Marve10s wants to merge 1 commit into
Effect-TS:mainfrom
Marve10s:fix-tx-recovered-retry-spin
Open

Marve10s wants to merge 1 commit into
Effect-TS:mainfrom
Marve10s:fix-tx-recovered-retry-spin

Conversation

@Marve10s

Copy link
Copy Markdown
Contributor

Effect.tx reruns its body in a tight loop, using a full CPU core, when the body recovers from Effect.txRetry instead of letting it propagate. The documented behavior is that a retry waits until a transactional value the body read changes. A common way to hit it is observing a blocking Tx operation inside a larger transaction, for example Effect.tx(Effect.exit(TxQueue.take(queue))) on an empty queue, or Effect.ignoreCause(TxSemaphore.acquire(sem)).

txRetry marks the transaction for retry and fails the body with an interrupt cause. The wait for a change, awaitPendingTransaction, only runs in the tapCause on the failure path. When the body turns that cause into a success, step still sees the retry flag, discards the result and reruns at once without waiting. This adds the same interruptible wait on the success path, so a recovered retry parks until a read value changes, as an unrecovered one already does. The failure path from #8780 is unchanged. Interrupting a transaction parked this way rolls back its writes and removes its waiters, like the existing path. A recovered retry still never commits its attempt; it only stops spinning.

Verification

Attempts made by a transaction blocked for 300 ms, on effect@4.1.4 (same on main):

control  tx(take): attempts while blocked=1, cpu=3ms of 300ms
tx(exit(take)): attempts while blocked=20538, cpu=386ms of 300ms
tx(take.catchCause): attempts while blocked=29470, cpu=371ms of 300ms
tx(ignoreCause(acquire)): attempts while blocked=30525, cpu=333ms of 300ms
tx(txRetry.matchCause): attempts while blocked=92256, cpu=309ms of 300ms

On this branch, with the same final results:

control  tx(take): attempts while blocked=1, cpu=2ms of 300ms
tx(exit(take)): attempts while blocked=1, cpu=1ms of 300ms
tx(take.catchCause): attempts while blocked=1, cpu=1ms of 300ms
tx(ignoreCause(acquire)): attempts while blocked=1, cpu=1ms of 300ms
tx(txRetry.matchCause): attempts while blocked=1, cpu=2ms of 300ms

The two new tests in Effect.test.ts cover waiting after a recovered retry and interrupting the parked transaction; both fail without the change. pnpm check, pnpm lint and the effect test project pass locally, apart from three Bash completion tests that also fail on main in my environment.

@changeset-bot

changeset-bot Bot commented Nov 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1d94a64

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 35 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/vitest Patch
@effect/ai-amazon-bedrock Patch
@effect/ai-anthropic Patch
@effect/ai-cloudflare Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/ai-typesafe Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-cloudflare Patch
@effect/platform-deno Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/graphql-generator Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
arbitrary-combinators.ts 39.88 KB 39.88 KB 0.00 KB (0.00%)
basic.ts 7.31 KB 7.31 KB 0.00 KB (0.00%)
batching.ts 11.21 KB 11.21 KB 0.00 KB (0.00%)
brand.ts 7.00 KB 7.00 KB 0.00 KB (0.00%)
cache.ts 11.73 KB 11.73 KB 0.00 KB (0.00%)
config.ts 23.09 KB 23.09 KB 0.00 KB (0.00%)
differ.ts 22.20 KB 22.20 KB 0.00 KB (0.00%)
http-client.ts 27.44 KB 27.44 KB 0.00 KB (0.00%)
http-router.ts 37.35 KB 37.35 KB 0.00 KB (0.00%)
logger.ts 12.19 KB 12.19 KB 0.00 KB (0.00%)
metric.ts 9.65 KB 9.65 KB 0.00 KB (0.00%)
optic.ts 7.21 KB 7.21 KB 0.00 KB (0.00%)
pubsub.ts 16.21 KB 16.21 KB 0.00 KB (0.00%)
queue.ts 13.09 KB 13.09 KB 0.00 KB (0.00%)
schedule.ts 11.99 KB 11.99 KB 0.00 KB (0.00%)
schema-bigdecimal.ts 14.29 KB 14.29 KB 0.00 KB (0.00%)
schema-binary.ts 42.45 KB 42.45 KB 0.00 KB (0.00%)
schema-class.ts 21.96 KB 21.96 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 30.56 KB 30.56 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 28.26 KB 28.26 KB 0.00 KB (0.00%)
schema-string-transformation.ts 15.54 KB 15.54 KB 0.00 KB (0.00%)
schema-string.ts 12.50 KB 12.50 KB 0.00 KB (0.00%)
schema-template-literal.ts 16.43 KB 16.43 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 39.41 KB 39.41 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 26.39 KB 26.39 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 21.11 KB 21.11 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 21.28 KB 21.28 KB 0.00 KB (0.00%)
schema-toFormatter.ts 21.37 KB 21.37 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 26.42 KB 26.42 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 21.45 KB 21.45 KB 0.00 KB (0.00%)
schema.ts 21.05 KB 21.05 KB 0.00 KB (0.00%)
stm.ts 14.46 KB 14.40 KB +0.06 KB (+0.43%)
stream.ts 10.56 KB 10.56 KB 0.00 KB (0.00%)

This branch is waiting to be deployed

1 waiting deployment
fork — 1d94a647 Waiting Nov 13, 2026 by Marve10s via approval-gate #26156
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant