Skip to content

test: observe grain directory convergence in liveness tests - #10299

Merged
ReubenBond merged 7 commits into
mainfrom
reubenbond-fix-liveness-test-flakiness
Jul 31, 2026
Merged

ReubenBond merged 7 commits into
mainfrom
reubenbond-fix-liveness-test-flakiness

Conversation

@ReubenBond

@ReubenBond ReubenBond commented Jul 22, 2026

Copy link
Copy Markdown
Member

Liveness_Grain_4_Kill_Silo_1_With_Timers can fail immediately after WaitForLivenessToStabilizeAsync returns because membership-status convergence and grain-directory convergence are handled by separate asynchronous consumers.

The existing stabilization helper waits until each silo's ISiloStatusOracle excludes the killed silo. The grain directory independently consumes membership changes to remove registrations and cache entries for activations on dead silos. The status event can therefore complete the helper just before the directory applies the same membership version, allowing the next grain call to route to a stale activation and time out.

This change keeps convergence tracking entirely outside the runtime directory implementations:

  • LocalGrainDirectory emits a diagnostic after applying a membership snapshot and purging stale directory/cache entries.
  • GrainDirectoryPartition emits a diagnostic after observing a membership view; its existing range-operation start/completion diagnostics describe asynchronous range transitions.
  • GrainDirectoryObserver, owned by in-process test clusters and subscribed before silos start, tracks those events and pending range operations externally.
  • Liveness stabilization waits for the observer between silo-status and gateway convergence when all silo service providers are available in-process.

DistributedGrainDirectory retains its original behavior and has no additional state or task tracking. There are no fixed delays, polling loops, or grain-call retries.

Liveness_Grain_4_Kill_Silo_1_With_Timers (and related hard-kill
liveness tests) can spuriously fail with a TimeoutException right
after killing a silo.

WaitForLivenessToStabilizeAsync computes a wait based on
ProbeTimeout * NumMissedProbesLimit, but ClusterMembershipOptions.
ExtendProbeTimeoutDuringDegradation (enabled by default) adaptively
extends probe timeouts under local health degradation, which is
common on loaded CI machines. This makes the actual failure
detection + directory convergence time exceed the test's
precomputed wait, so the very next grain call can hit the 30s
response timeout before the cluster finishes reacting to the kill.

Retry transient TimeoutExceptions for the post-recovery traffic in
Do_Liveness_OracleTest_2, bounded by a 60s window, so the test
tolerates slow convergence without weakening its assertions.
Copilot AI review requested due to automatic review settings July 22, 2026 15:37
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI 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.

Pull request overview

This PR improves the reliability of Orleans liveness/membership tests by retrying post-recovery grain calls which can transiently time out after a hard silo kill (where failure detection and directory convergence are timing-sensitive under CI load).

Changes:

  • Wrap post-recovery grain traffic in Do_Liveness_OracleTest_2 with a retry-on-timeout helper.
  • Add SendTrafficWithRetry to tolerate transient TimeoutExceptions for a bounded period after kill/restart.
  • Add System.Diagnostics usage for retry window timing.
Show a summary per file
File Description
test/Orleans.Runtime.Tests/MembershipTests/LivenessTests.cs Adds bounded retry logic around post-recovery grain calls to reduce spurious liveness test failures under load.

Copilot's findings

Comments suppressed due to low confidence (1)

test/Orleans.Runtime.Tests/MembershipTests/LivenessTests.cs:186

  • To support a shared retry window across all post-recovery grain calls, SendTrafficWithRetry should accept the caller's Stopwatch/window instead of starting a new one per call. This prevents multiplying the retry budget by the number of grains.
        private async Task SendTrafficWithRetry(long key, bool startTimers = false)
        {
            var stopwatch = Stopwatch.StartNew();
            var retryWindow = TimeSpan.FromSeconds(60);
            while (true)
  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Comment thread test/Orleans.Runtime.Tests/MembershipTests/LivenessTests.cs Outdated
Wait for LocalGrainDirectory to apply the membership version observed by each active silo before liveness stabilization completes. The version signal is published after stale directory and cache entries are removed, closing the race between silo-status convergence and grain routing without polling, sleeps, or call retries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965b2b7b-61b4-474d-ac47-bec64d97ff6c
@ReubenBond ReubenBond changed the title test: retry post-recovery grain calls in liveness tests test: await grain directory convergence in liveness tests Jul 22, 2026
Copilot AI review requested due to automatic review settings July 22, 2026 15:57

Copilot AI 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.

Copilot's findings

Comments suppressed due to low confidence (1)

src/Orleans.TestingHost/LivenessStabilizationHelper.cs:56

  • WaitForExpectedActiveSilosAsync can now wait up to timeout for ISiloStatusOracle convergence and then wait up to another full timeout for grain-directory cleanup, so this method can exceed its timeout parameter. If timeout is intended as an overall bound (as implied by callers), consider using the remaining time budget for the grain-directory phase.
            waitTasks = testHooks.Select(hooks => hooks.WaitForGrainDirectoryMembershipVersion(timeout));
            results = await Task.WhenAll(waitTasks).WaitAsync(timeout);
            return results.All(static result => result);
  • Files reviewed: 4/4 changed files
  • Comments generated: 3

Comment thread src/Orleans.TestingHost/LivenessStabilizationHelper.cs
Comment thread src/Orleans.TestingHost/LivenessStabilizationHelper.cs Outdated
Comment thread src/Orleans.Runtime/Silo/TestHooks/TestHooksSystemTarget.cs Outdated
Emit membership-version-applied diagnostics after local directory cleanup and after distributed partition transitions complete. Test hooks subscribe to the aggregate directory event using a check-subscribe-check pattern instead of calling a waiter on LocalGrainDirectory.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965b2b7b-61b4-474d-ac47-bec64d97ff6c
Copilot AI review requested due to automatic review settings July 22, 2026 19:13

Copilot AI 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.

Copilot's findings

Comments suppressed due to low confidence (2)

src/Orleans.TestingHost/LivenessStabilizationHelper.cs:29

  • WaitForLivenessToStabilizeAsync logs that it is waiting "up to" the provided timeout, but this helper no longer tracks elapsed time. Since subsequent waits use the full timeout again, the overall stabilization wait can exceed the caller-provided budget, slowing/derailing tests.
    public static async Task<bool> WaitForExpectedActiveSilosAndGatewaysAsync(
        IReadOnlyCollection<SiloHandle> activeSilos,
        IReadOnlyCollection<ITestHooks> testHooks,
        GatewayManager gatewayManager,
        TimeSpan timeout)
    {
        ArgumentNullException.ThrowIfNull(gatewayManager);

        if (!await WaitForExpectedActiveSilosAsync(activeSilos, testHooks, timeout))
        {
            return false;
        }

        return await WaitForExpectedActiveGatewaysAsync(activeSilos, gatewayManager, timeout);
    }

src/Orleans.TestingHost/LivenessStabilizationHelper.cs:56

  • This method now performs two separate waits (liveness oracle convergence, then grain-directory convergence) each with the full timeout, so it can take up to ~2× the intended stabilization budget. Consider accounting for elapsed time before starting the second phase so the method honors the provided overall timeout.
        try
        {
            var waitTasks = testHooks.Select(hooks => hooks.WaitForActiveSilos(expectedActiveSilos, timeout));
            var results = await Task.WhenAll(waitTasks).WaitAsync(timeout);
            if (!results.All(static result => result))
            {
                return false;
            }

            waitTasks = testHooks.Select(hooks => hooks.WaitForGrainDirectoryMembershipVersion(timeout));
            results = await Task.WhenAll(waitTasks).WaitAsync(timeout);
            return results.All(static result => result);
  • Files reviewed: 7/7 changed files
  • Comments generated: 2

Comment thread src/Orleans.TestingHost/LivenessStabilizationHelper.cs
Comment thread src/Orleans.Runtime/Silo/TestHooks/TestHooksSystemTarget.cs Outdated
Keep grain directory runtime behavior unchanged and emit only local-applied and partition-observed diagnostics. In-process test clusters subscribe before silo startup and track membership versions plus existing range-operation lifetimes externally, allowing liveness stabilization to await directory convergence without runtime task tracking or state.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965b2b7b-61b4-474d-ac47-bec64d97ff6c
@ReubenBond ReubenBond changed the title test: await grain directory convergence in liveness tests test: observe grain directory convergence in liveness tests Jul 22, 2026
Copilot AI review requested due to automatic review settings July 22, 2026 20:28

Copilot AI 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.

Copilot's findings

Comments suppressed due to low confidence (2)

src/Orleans.TestingHost/LivenessStabilizationHelper.cs:33

  • timeout is reused for the silo-status wait, grain-directory convergence wait, and gateway convergence wait, so the overall stabilization can exceed the intended timeout. Track elapsed time and pass the remaining time to each subsequent stage.
        if (waitForGrainDirectoryConvergence is not null
            && !await waitForGrainDirectoryConvergence(timeout))
        {
            return false;
        }

src/Orleans.TestingHost/LivenessStabilizationHelper.cs:49

  • When activeSilos.Count == 0, this helper returns immediately without waiting, even though callers/logging treat it as a stabilization wait. This was previously a delay for the full timeout; consider keeping that behavior to avoid surprising fast-fail paths.
        if (activeSilos.Count == 0)
        {
            return false;
        }
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new

Use a single timeout budget across silo-status, grain-directory, and gateway convergence, and preserve the existing fallback delay when no active silos are available.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965b2b7b-61b4-474d-ac47-bec64d97ff6c
Copilot AI review requested due to automatic review settings July 31, 2026 16:36

Copilot AI 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.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 1

Comment thread src/Orleans.TestingHost/InProcTestCluster.cs Outdated
Do not await runtime grain-directory diagnostics when InProcessTestCluster uses its custom InProcessGrainDirectory, which does not emit those events.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965b2b7b-61b4-474d-ac47-bec64d97ff6c
Copilot AI review requested due to automatic review settings July 31, 2026 17:16

Copilot AI 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.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new

Only enable directory convergence observation when each silo uses the built-in local or distributed directory. External default directory implementations do not emit the required diagnostics, so retain the existing liveness stabilization path for those clusters.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965b2b7b-61b4-474d-ac47-bec64d97ff6c
Copilot AI review requested due to automatic review settings July 31, 2026 17:24

Copilot AI 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.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new

@ReubenBond
ReubenBond merged commit cc331e5 into main Jul 31, 2026
69 of 70 checks passed
@ReubenBond
ReubenBond deleted the reubenbond-fix-liveness-test-flakiness branch July 31, 2026 18:13
This was referenced Aug 28, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants