Repository navigation
Hand cluster shards off before final cleanup on shutdown (v3) - #8946
Merged
tim-smart merged 10 commits intoOct 9, 2026
Merged
Conversation
🦋 Changeset detectedLatest commit: 0f5ec6a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
…the final releaseAll (v3) Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Move the reproductions into Sharding.test.ts, instrument entity teardown to assert teardown-before-release, and close runners while advancing the TestClock so cleanup cannot hang.
Draining unregisters the runner, stops shard acquisition and releases each shard lock after its entities are torn down. Scope close drains first, bounded by entityTerminationTimeout plus the lock interval, before the final releaseAll.
…e during drain The empty acquisition case fails until drain re-checks completion after a successful acquire that returns no shards.
Replace the drain-based tests with shutdown-only coverage: entity and singleton teardown before shard release, closing and nested singleton registrations, no acquisition once shutdown starts, and duplicate entity ids across shards.
Backport the v4 shutdown handoff: remove Sharding.drain, stop acquiring once shutdown starts, release every shard after its entities and singletons stop in the Sharding finalizer, serialize registration with unregistration, and look entities up by address when interrupting a shard.
effect-bot
force-pushed
the
failing-tests/v3-graceful-drain
branch
from
October 9, 2026 11:03
2cb6a8e to
fe13740
Compare
…ndoff Also clarify that each lock is released individually and that only the handoff wait is bounded.
Merged
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.
Summary
When
Shardingshuts down, it now hands its shards off after their entities and singletons stop, releasing each lock individually before the finalreleaseAll. Live runners can take the shards over without waiting for lock leases to expire. There's no new public API: disposing the layer, e.g. on SIGTERM, starts the handoff. This backports the design of themainPR, #8941.Changes
Sharding.ts:Shutdown start. Once shutdown starts, including the preemptive shutdown triggered by entity finalizers, the runner stops acquiring shards. Registration and unregistration are serialized, so a registration that's in flight can't re-advertise a runner that is shutting down.
Handoff. The Sharding finalizer runs shutdown, then the handoff:
RunnerStorage.releaseonce its entities and singletons have stopped.While handing off, the pending shards are released in one batch, so 600 shards don't each need their own release fiber. The trade-off is head-of-line blocking: one slow entity or singleton teardown delays every lock in the batch, and one slow storage release delays the ones after it. Shards whose ordinary release was already running when shutdown started are left to that release.
Best effort. Only the handoff wait is bounded, by the shard lock interval. If it times out, the final
releaseAllattempts to release the remaining locks. Total shutdown is not bounded: unregistering and the finalreleaseAllcan still wait on storage. Finalizers are uninterruptible, so the wait is made interruptible for its timeout. Release forks run in theFiberSet, because forks into the closing sharding scope never run.Singletons. Shard releases now wait for the singletons on those shards to stop. That includes singletons whose registration scope is already closing. A singleton is not restarted while its previous instance is still stopping. Singleton teardown is awaited outside the singleton lock, so a singleton that registered another singleton can still be closed.
internal/entityManager.ts:interruptShardbecameinterruptShards, which takes several shards, so each entity manager needs one fiber per batch.Tests
In
packages/cluster/test/Sharding.test.ts, underSharding graceful shutdown, one test per behaviour change:releaseAll, tearing down entities firstValidation
Sharding.test.ts: 34/34. On unmodifiedv3sources, four of the five tests above fail; the unhealthy-storage test covers the new handoff wait. Removing the shutdown acquire guard, the singleton teardown wait, or the interruptible handoff wait each fails the matching test.ClusterWorkflowEngine, run alone: about 2.9s for its slowest test, the same asv3(about 2.85s).@effect/clustertests other thanSqlRunnerStoragepassed 111/111 before the test reduction.SqlRunnerStorageneeds Docker containers, which were unavailable locally; it doesn't useSharding.platform-nodeNodeClusterSocket.test.ts: pass.tsc -bfor@effect/cluster: pass. eslint anddocgenpassed before the test reduction.Closes EFF-1884