Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

harden CircuitBreakerSpecs.cs #7418

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/core/Akka.Tests/Pattern/CircuitBreakerSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.ExceptionServices;
Expand Down Expand Up @@ -85,11 +86,11 @@ public async Task Must_increment_failure_count_on_callTimeout_before_call_finish
var breaker = ShortCallTimeoutCb();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
// meant to run as detached task
Task.Run(() => breaker.Instance.WithSyncCircuitBreaker(() => Thread.Sleep(Dilated(TimeSpan.FromSeconds(1)))));
var t = Task.Run(() => breaker.Instance.WithSyncCircuitBreaker(() => Thread.Sleep(Dilated(TimeSpan.FromSeconds(1)))));
await AwaitConditionAsync(() => t.Status >= TaskStatus.Running); // need to kick off the task before we can check the latch
Copy link
Member Author

Choose a reason for hiding this comment

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

This spec is pretty racy due to non-determinism of the TaskScheduler - now we force our assertions to wait until the Task.Run actually gets scheduled for execution, because otherwise the breaker never gets a chance to be scheduled and this will throw the timing off.

#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
var epsilon = TimeSpan.FromMilliseconds(500); // need to pad timeouts due to non-determinism of OS scheduler
await WithinAsync(TimeSpan.FromMilliseconds(900) + epsilon,
() => AwaitConditionAsync(() => breaker.Instance.CurrentFailureCount == 1, Dilated(TimeSpan.FromMilliseconds(100)), TimeSpan.FromMilliseconds(100)));
await AwaitConditionAsync(() => breaker.Instance.CurrentFailureCount == 1, TimeSpan.FromMilliseconds(900) + epsilon, TimeSpan.FromMilliseconds(100));
}
}

Expand Down Expand Up @@ -226,10 +227,10 @@ public void Must_increment_failure_count_on_async_failure()
public async Task Must_reset_failure_count_after_success()
{
var breaker = MultiFailureCb();
_ = breaker.Instance.WithCircuitBreaker(() => Task.Run(SayHi));
Enumerable.Range(1, 4).ForEach(_ => breaker.Instance.WithCircuitBreaker(() => Task.Run(ThrowException)));
await WaitForTaskToBeScheduled(breaker.Instance.WithCircuitBreaker(() => Task.Run(SayHi)));
await WaitForTaskToBeScheduled(Enumerable.Range(1, 4).Select(_ => breaker.Instance.WithCircuitBreaker(() => Task.Run(ThrowException))).ToList());
Copy link
Member Author

Choose a reason for hiding this comment

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

Issues we've been running into this spec are due to scheduler lag again - when the CPU count is low these tasks may not get scheduled to run immediately.

await AwaitAssertAsync(() => breaker.Instance.CurrentFailureCount.ShouldBe(4), AwaitTimeout);
_ = breaker.Instance.WithCircuitBreaker(() => Task.Run(SayHi));
await WaitForTaskToBeScheduled(breaker.Instance.WithCircuitBreaker(() => Task.Run(SayHi)));
await AwaitAssertAsync(() => breaker.Instance.CurrentFailureCount.ShouldBe(0), AwaitTimeout);
}

Expand Down Expand Up @@ -354,6 +355,16 @@ public class CircuitBreakerSpecBase : AkkaSpec

public bool CheckLatch(CountdownEvent latch) => latch.Wait(AwaitTimeout);

public Task WaitForTaskToBeScheduled(Task childTask)
{
return AwaitConditionAsync(() => childTask.Status >= TaskStatus.Running);
}

public Task WaitForTaskToBeScheduled(IReadOnlyCollection<Task> childTasks)
{
return AwaitConditionAsync(() => childTasks.All(t => t.Status >= TaskStatus.Running));
}

[DebuggerStepThrough]
public static void ThrowException() => throw new TestException("Test Exception");

Expand Down
Loading