You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
De-flake ClusterShardingSpec family: re-send Gets that race shard hand-off (#8500)
`ClusterShardingSpec` (base class for the Persistent/DData/WithEntityRecovery
variants) failed in CI on both transports with the same shape: one node timed
out waiting for an `Int32` counter reply and every other node then failed the
next barrier.
Mechanism. `HandOffStopper` replies `ShardStopped` the moment the last entity
terminates - before the `Shard` actor stops, and before the `ShardRegion`
observes `Terminated` and drops the shard from `_shards`/`_regionByShard`. The
spec treats `ShardStopped` as "the shard is gone" and immediately sends a `Get`
through the region, so the region forwards it into the dying shard:
[.../RememberCounterEntitiesRegion/1] DeadLetter from [.../system/testActor1]
to [.../RememberCounterEntitiesRegion/1]: Get { CounterId = 13 }
Sharding is at-most-once, so the request has to be re-sent, not merely
re-awaited. Two sites did not do that:
* `recover_entities_upon_restart` wrapped `Get(13)` in `AwaitAssertAsync(5s)`
but the inner `ExpectMsgAsync(0)` carried no bound, so it inherited
`akka.test.single-expect-default` (5s) - the entire budget. The loop made
exactly one attempt:
AwaitAssert failed, timeout [00:00:05] is over after [1] attempts
and [00:00:05.0055266] elapsed time
* `permanently_stop_entities_which_passivate` sent `Get(25)` as a bare
one-shot, so a single dropped message was an unconditional failure.
Both now re-send with a fresh probe per attempt and a 1s per-attempt bound, so
the 5s budget admits four sends instead of one. A fresh probe per attempt also
keeps a late reply from a timed-out attempt out of the next attempt's queue;
the same defect made the `Identify(4)` retry loop in the second phase a no-op,
and it is bounded now too.
The second phase also ran its three barriers inside `WithinAsync(15s)`.
`EnterBarrierAsync` derives its timeout from `RemainingOr(barrier-timeout)`, so
the rendezvous got the Within remainder instead of the configured 70s:
EnterBarrier(Name: after-13, Role: [RoleName(sixth)], Timeout:00:00:13.8157551)
timeout while waiting for barrier 'after-13'
The Within is removed, matching the treatment already applied to
`recover_entities_upon_restart`; its 15s is re-attached to the two waits it was
actually protecting, so no wait ends up with a smaller budget than before.
Verified by widening the hand-off window in a local throwaway build (delaying
the Shard's stop after `ShardStopped`), which reproduces both CI signatures
exactly - `Timeout 00:00:05 ... System.Int32` with `barrier failed:after-shard-restart`,
and `Timeout 00:00:13.9 ... System.Int32` with `barrier failed:after-13` - and
passes with these changes.
Test-only change.
0 commit comments