refactor(host)/test: M2.14 review nits — simplify probe cancellation + pre-cancelled-token test (#28)
- Remove redundant linked CancellationTokenSource in ProbeAsync; pass the framework cancellationToken and ProbeTimeout directly to Ask (the two-CTS pattern was redundant — Ask already honours both the timeout and the token). - Add EchoActor XML <remarks> explaining why no Receive<Identify> handler is needed (ActorBase answers Identify automatically). - Add PreCancelledToken_ReportsUnhealthy_DoesNotThrow test: verifies the never-throws guarantee on the shutdown-race path (token already cancelled before CheckHealthAsync is invoked).
This commit is contained in:
@@ -157,11 +157,9 @@ public sealed class RequiredSingletonsHealthCheck : IHealthCheck
|
||||
// ActorSelection so a missing path resolves an ActorIdentity with a null
|
||||
// Subject (rather than throwing) within the bounded timeout.
|
||||
var selection = system.ActorSelection($"/user/{proxyName}");
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(ProbeTimeout);
|
||||
|
||||
var identity = await selection
|
||||
.Ask<ActorIdentity>(new Identify(proxyName), ProbeTimeout, cts.Token)
|
||||
.Ask<ActorIdentity>(new Identify(proxyName), ProbeTimeout, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return (proxyName, identity.Subject is not null);
|
||||
|
||||
@@ -27,6 +27,10 @@ public class RequiredSingletonsHealthCheckTests : TestKit
|
||||
{
|
||||
/// <summary>A minimal live actor that does nothing — its mere existence makes
|
||||
/// an <see cref="Identify"/> resolve a non-null Subject (i.e. "reachable").</summary>
|
||||
/// <remarks>No <c>Receive<Identify></c> handler is needed: Akka's
|
||||
/// <see cref="ActorBase"/> answers every <see cref="Identify"/> message with
|
||||
/// an <see cref="ActorIdentity"/> automatically, so an empty actor at the proxy
|
||||
/// path is sufficient to simulate a reachable singleton.</remarks>
|
||||
private sealed class EchoActor : ReceiveActor
|
||||
{
|
||||
}
|
||||
@@ -106,4 +110,34 @@ public class RequiredSingletonsHealthCheckTests : TestKit
|
||||
|
||||
Assert.Equal(HealthStatus.Unhealthy, result.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreCancelledToken_ReportsUnhealthy_DoesNotThrow()
|
||||
{
|
||||
// Shutdown-race path: CheckHealthAsync is called with an already-cancelled
|
||||
// token (e.g. host is tearing down). The check must never throw — any
|
||||
// OperationCanceledException from Ask must be caught and mapped to Unhealthy.
|
||||
foreach (var name in RequiredSingletonsHealthCheck.RequiredSingletonProxyNames)
|
||||
{
|
||||
Sys.ActorOf(Props.Create(() => new EchoActor()), name);
|
||||
}
|
||||
|
||||
var check = new RequiredSingletonsHealthCheck(
|
||||
ProviderReturning(Sys),
|
||||
NullLogger<RequiredSingletonsHealthCheck>.Instance);
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel(); // already cancelled before the check runs
|
||||
|
||||
var context = new HealthCheckContext
|
||||
{
|
||||
Registration = new HealthCheckRegistration(
|
||||
"required-singletons", check, failureStatus: null, tags: null),
|
||||
};
|
||||
|
||||
// Must not throw; an already-cancelled token → all probes fail → Unhealthy.
|
||||
var result = await check.CheckHealthAsync(context, cts.Token);
|
||||
|
||||
Assert.Equal(HealthStatus.Unhealthy, result.Status);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user