fix(opcuaclient): shutdown cancels session work before the gate wait; dispose skips a redundant nested shutdown (review r2 R2-1, R2-2)
- R2-1: the ConditionRefresh sweeps (RefreshRequired, post-outage recovery) run under the session's work token, and ShutdownAsync cancels session work first thing - so a sweep holding the subscription gate against a stalling upstream no longer eats the whole teardown budget the deletes and close need. - R2-2: DisposeAsync skips its nested ShutdownAsync when a shutdown already started for the current lifecycle generation (nothing initialised since), so PostStop's dispose does not re-wait the gate past its budget. Test: with the gate held, the shutdown waits (positive control) and the dispose does not wait again; red with the skip removed. Refs #743 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
+21
@@ -257,4 +257,25 @@ public sealed class OpcUaClientLifecycleGenerationTests
|
||||
session.Verify(s => s.AddSubscription(It.IsAny<Subscription>()), Times.Once,
|
||||
"the late subscribe must not have created anything on the closing session");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// r2 R2-2: PostStop runs ShutdownAsync then DisposeAsync. With subscription management contended (a subscribe
|
||||
/// still holding the gate), the shutdown spends its budget waiting on the gate; the dispose that follows must not
|
||||
/// re-run a nested shutdown and wait all over again. Positive control: the shutdown itself did wait.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Dispose_after_a_shutdown_of_the_same_lifecycle_does_not_rewait_the_gate()
|
||||
{
|
||||
var drv = new OpcUaClientDriver(Options(), "opcua-r22") { TeardownBudget = TimeSpan.FromSeconds(3) };
|
||||
drv.SetSessionForTest(NewSession().Object);
|
||||
await drv.SubscriptionGate.WaitAsync(TestContext.Current.CancellationToken); // a "subscribe" that never finishes
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
await drv.ShutdownAsync(TestContext.Current.CancellationToken);
|
||||
sw.Elapsed.ShouldBeGreaterThan(TimeSpan.FromSeconds(2), "positive control: the shutdown waited on the held gate");
|
||||
|
||||
sw.Restart();
|
||||
await drv.DisposeAsync();
|
||||
sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(2), "the dispose must not wait the 3 s gate budget a second time");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user