Repository navigation
Cluster.Sharding: a coordinator hand-over only stops the local ShardRegion when the node is leaving - #8586
Merged
Aaronontheweb merged 1 commit intoSep 11, 2026
Conversation
…egion when the node is leaving ClusterSingletonManager sends the coordinator its termination message on every hand-over, and a hand-over is not always a shutdown: while a cluster forms, two members of the same role can both reach Oldest, and the loser hands over while staying Up. ShardCoordinator.HandleTerminate read every termination message as a node shutdown, sent GracefulShutdown to the node's own live region, and deferred its stop until the region was gone. ClusterShardingGuardian then evicted the dead region from ClusterSharding's cache, so ShardRegion(typeName) threw on that node for the rest of the process. The coordinator still stops on every hand-over, which is what lets the singleton manager complete it. It only takes the local region down with it when that region is already handing off, which is the coordinated shutdown path, or when the node itself is leaving: the system is terminating, CoordinatedShutdown is running, or the self member is Leaving, Exiting, or Down. The branch that sends GracefulShutdown to a live local region came from JVM Akka PR 30338, a rolling-update latency optimization; before it a termination message was a bare stop, which is what a benign hand-over now gets again. ShardCoordinatorHandOverSpec sends the singleton's termination message the way the manager does and asserts that the coordinator stops and the region does not. It fails on the old code with the region's Terminated. Fixes #8583.
Aaronontheweb
commented
Sep 11, 2026
Aaronontheweb
left a comment
Member
Author
There was a problem hiding this comment.
LGTM - needs backport to v1.5
Aaronontheweb
deleted the
fix/shard-coordinator-handover-keeps-local-region
branch
September 11, 2026 14:20
Aaronontheweb
added a commit
that referenced
this pull request
Sep 12, 2026
Adds a new #### 1.5.72 TBD #### section covering every user-visible change in this backport: the TestKit.Xunit async dispose chain and its CS0114 source break (#8545), the DotNetty batching override fix (#8561), the new TestKitBase.ShutdownAsync API (#8499), the MultiNodeTestRunner conductor port race fix and its node-exit backstop (#8515), the Streams Tcp Unbind timing fix (#8570), the Sharding remember-entities write-timeout fix with a migration note (#8574), the Replicator.IsKnownNode fix (#8582), the ShardCoordinator hand-over fix (#8586), the LeaveSelf re-send fix, and a one-line summary of the de-flaked specs.
Aaronontheweb
added a commit
that referenced
this pull request
Sep 12, 2026
Adds a new #### 1.5.72 TBD #### section covering every user-visible change in this backport: non-blocking Cluster extension startup and its startup-ordering follow-up (#8359, #8580), the TestKit.Xunit async dispose chain and its CS0114 source break (#8545), the DotNetty batching override fix (#8561), the new TestKitBase.ShutdownAsync API (#8499), the MultiNodeTestRunner conductor port race fix and its node-exit backstop (#8515), the Streams Tcp Unbind timing fix (#8570), the Sharding remember-entities write-timeout fix with a migration note (#8574), the Replicator.IsKnownNode fix (#8582), the ShardCoordinator hand-over fix (#8586), and a one-line summary of the de-flaked specs.
This was referenced Oct 4, 2026
Merged
Closed
This was referenced Oct 5, 2026
Closed
This was referenced Oct 13, 2026
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.
What changes
ShardCoordinator.HandleTerminateno longer treats every termination message from itsClusterSingletonManageras a node shutdown. It still stops the coordinator on every hand-over, which is what lets the singleton manager complete the hand-over. It only tears the node's ownShardRegiondown with it when that region is already handing off, which is the coordinated-shutdown path, or when the node itself is leaving: the actor system is terminating,CoordinatedShutdownis running, or the self member isLeaving,Exiting, orDown. On a hand-over that is not a shutdown the region keeps running and the coordinator logs one debug line saying so.A new single-node spec,
ShardCoordinatorHandOverSpec, sends the singleton's termination message the way the manager does and asserts that the coordinator stops and the region does not. On dev it fails with "Expected no messages, instead we received Terminated [.../system/sharding/Entity]".No public API, wire, or configuration change, so no ledger row; this restores the intent of a block that was written for the leaving case.
Why
Fixes #8583.
ClusterShardingMinMembersPerRoleNotConfiguredSpechas failed six times in two days on PRs that do not touch sharding, on both transports. The chain is the same in every log:min-nr-of-members = 2, two nodes act as leader on disjoint views and assign conflicting up-numbers. Two members of the same role both reachOldestfor the coordinator singleton.Oldest -> WasOldest -> HandingOver) and staysUp. The manager sends the coordinator its termination message, as it does on every hand-over.GracefulShutdown, and defers its stop until the region is gone.ClusterShardingGuardianwatches the region and removes it fromClusterSharding's cache when it stops. From then onClusterSharding.Get(sys).ShardRegion(typeName)throws on that node for the life of the process.Steps 1 and 2 are by design and match Pekko line for line: the
TakeOverFromMe/HandOverToMeexchange exists for exactly this, and it settled in about half a second in every log. Step 3 is the fault. The branch that sendsGracefulShutdownto a live local region came from JVM Akka PR 30338 in 2021, a latency optimization for rolling updates whose own issue text says things work fine without it. Before that a termination message was a bare stop, which is still what the DData and persistent coordinators do on their other stop paths. So the change routes a benign hand-over back to what the coordinator did for six years before that optimization, and keeps the optimization on every real shutdown route.The same gate also covers
LeaseLostin the singleton manager, which sends the same termination message on a route that is not a shutdown at all.Neither #8359 nor #8580 is involved: in every new log the racing pair is two joiners, never the self-joiner, and the up-number assignment, leader choice, and convergence check are unchanged and identical to upstream.
Safety on the real shutdown routes
CoordinatedShutdown.Run, any reasonShutdownReasonis set by the compare-and-swap at the top ofRun, before any phaseCluster.Leave(self), region drained firstCluster.Leave(self), peer'sHandOverToMebeats our own shutdown phaseLeaving.Leavingis part of the convergence set, so the leader can only move us toExitingafter we have seen that gossip, and a peer only becomes oldest onMemberExitedDown, or a staleUp; a stale read costs nothing because the region already skips its own drain when self isDownCluster.IsTerminatedRemovedis deliberately not in the check: the read view synthesizes aRemovedself member whenever self is absent from its snapshot, including before the node has joined, and a real removal stops the singleton manager outright.How it was checked
dotnet build src/contrib/cluster/Akka.Cluster.Sharding.Tests -c Release -warnaserrorclean. The new spec fails on dev with the region'sTerminatedand passes with the change, ten of ten. The graceful-shutdown, leaving, and coordinated-shutdown specs in the unit project pass, and the multi-nodeClusterShardingGracefulShutdownOldestSpec,ClusterShardingGracefulShutdownSpec, andClusterShardingLeavingSpecpass; the first of those is the upstream gate for the optimization this touches.ClusterShardingMinMembersPerRoleNotConfiguredSpec, the spec that surfaced the fault, passes. The fullAkka.Cluster.Sharding.Testssuite passes.Akka.API.Testspasses with no approval change.