Skip to content

Fix cluster single-runner shutdown deadlock - #7032

Merged
tim-smart merged 2 commits into
Effect-TS:mainfrom
marbemac:fix/cluster-single-runner-shutdown-deadlock
Aug 5, 2026
Merged

Fix cluster single-runner shutdown deadlock#7032
tim-smart merged 2 commits into
Effect-TS:mainfrom
marbemac:fix/cluster-single-runner-shutdown-deadlock

Conversation

@marbemac

@marbemac marbemac commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Type

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Hit this running a single-node cluster locally: shutdown would just hang forever, spamming WARN No healthy runners available.

If an entity fires an outgoing message while it's finalizing (e.g. a reactor pushing an update to another entity) and the target shard is no longer routable, sendOutgoing retries EntityNotAssignedToRunner forever. On a single runner the shard never gets reassigned, so the finalizer never completes and the Sharding scope never closes. Multi-runner masks it since the shard just moves to a peer.

Fix: bail out of the retry when it provably can't succeed — the runner is shutting down, or the target's local entity manager is already closing/closed. RunnerUnavailable still retries so rolling deploys are unaffected, and persisted messages stay durable in storage.

Added a regression test that arms an entity finalizer to send during teardown and asserts the scope actually closes (both preemptive and non-preemptive shutdown).

@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: efd541e

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

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared 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/vitest 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

@pullfrog

pullfrog Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Run failed. View the logs →

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Rerun failed job ➔View workflow run | via Pullfrog | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@github-actions

github-actions Bot commented Aug 5, 2026

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
basic.ts 7.06 KB 7.06 KB 0.00 KB (0.00%)
batching.ts 9.86 KB 9.86 KB 0.00 KB (0.00%)
brand.ts 6.34 KB 6.34 KB 0.00 KB (0.00%)
cache.ts 10.71 KB 10.71 KB 0.00 KB (0.00%)
config.ts 20.73 KB 20.73 KB 0.00 KB (0.00%)
differ.ts 20.31 KB 20.31 KB 0.00 KB (0.00%)
http-client.ts 21.53 KB 21.53 KB 0.00 KB (0.00%)
logger.ts 10.84 KB 10.84 KB 0.00 KB (0.00%)
metric.ts 8.98 KB 8.98 KB 0.00 KB (0.00%)
optic.ts 7.18 KB 7.18 KB 0.00 KB (0.00%)
pubsub.ts 14.99 KB 14.99 KB 0.00 KB (0.00%)
queue.ts 11.66 KB 11.66 KB 0.00 KB (0.00%)
schedule.ts 10.83 KB 10.83 KB 0.00 KB (0.00%)
schema-class.ts 19.27 KB 19.27 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 29.09 KB 29.09 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.40 KB 25.40 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.42 KB 13.42 KB 0.00 KB (0.00%)
schema-string.ts 10.95 KB 10.95 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.21 KB 15.21 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 22.02 KB 22.02 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.45 KB 24.45 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.28 KB 19.28 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.11 KB 19.11 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.97 KB 18.97 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.69 KB 22.69 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.60 KB 19.60 KB 0.00 KB (0.00%)
schema.ts 18.52 KB 18.52 KB 0.00 KB (0.00%)
stm.ts 12.63 KB 12.63 KB 0.00 KB (0.00%)
stream.ts 9.80 KB 9.80 KB 0.00 KB (0.00%)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one suggestion to extend the regression test inline.

Reviewed changes

  • .changeset/fix-cluster-shutdown-deadlock.md: patch changeset for effect.
  • packages/effect/src/unstable/cluster/Sharding.ts: in sendOutgoing, abandon EntityNotAssignedToRunner retries when the runner is shutting down or the target entity manager is no longer alive; RunnerUnavailable still retries.
  • packages/effect/test/cluster/Sharding.test.ts: regression test that arms an entity finalizer to send a volatile message during teardown and asserts the Sharding scope closes for both preemptiveShutdown values.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔Fix 👍s ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

Comment thread packages/effect/test/cluster/Sharding.test.ts Outdated
@tim-smart
tim-smart enabled auto-merge (squash) August 5, 2026 22:54

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This re-review covered the second commit on the branch (efd541ebaa), which refined the initial single-runner shutdown deadlock fix.

  • Hoisted isPersisted out of the retry suspend in Sharding.sendOutgoing so the persisted annotation is resolved once per send instead of on every retry.
  • Persisted abandoned persisted messages during shutdown rather than dropping them: volatile messages are logged and abandoned, while persisted ones are saved via storage.saveRequest / storage.saveEnvelope and a warning is emitted.
  • Expanded the regression test to cover both persisted=true and persisted=false, and to assert the expected in-memory storage journal length after shutdown completes.
  • Test robustness improvements: replaced the bounded for wait loop with a shardAcquired latch, switched to Effect.forkDetach/interruptUnsafe for deterministic teardown, and added explicit timeouts.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@tim-smart
tim-smart merged commit 936b135 into Effect-TS:main Aug 5, 2026
15 of 17 checks passed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes since 74218fd

  • packages/effect/src/unstable/cluster/Sharding.ts: isPersisted is now read once before Effect.catchFilter so the abandon handler can branch on it; persisted outgoing messages are explicitly saved to MessageStorage during shutdown instead of being dropped.
  • packages/effect/test/cluster/Sharding.test.ts: the regression test now covers both persisted and preemptiveShutdown, asserts driver.journal.length, and uses a shardAcquired latch with cleanup and a shorter shutdown timeout.

The prior suggestion to cover the persisted path is resolved. Tests, lint, and type checks pass.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

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.

2 participants