fix(scripts): reset-aware handshake refuses a null reset value; document additive timeouts (#36 review)
A null resetValue encoded to null, which wrote an empty string and then handed the waiter a null target (= any change), fast-pathing on the stale cache and reinstating the race the overload exists to close. The context parameter is now non-nullable and the accessor throws ArgumentException before any write. Also: additive-timeout note on all surfaces, batch-failure message no longer names the bool overload, and the error-message/batch-failure branches are pinned. Claude-Session: https://claude.ai/code/session_01XPsuy2ZZ5Gbyfpt8PrK5BV
This commit is contained in:
+62
-1
@@ -24,7 +24,10 @@ public class WriteBatchResetAndWaitTests : TestKit
|
||||
new SharedScriptLibrary(
|
||||
new ScriptCompilationService(NullLogger<ScriptCompilationService>.Instance),
|
||||
NullLogger<SharedScriptLibrary>.Instance),
|
||||
currentCallDepth: 0, maxCallDepth: 10, askTimeout: TimeSpan.FromSeconds(5),
|
||||
// 30s: the legs Ask the InstanceActor with their own bounds (the batch's internal 35s,
|
||||
// the waits' own timeouts); a short ambient Ask timeout would race those and make the
|
||||
// reset/response phases indistinguishable from an AskTimeoutException.
|
||||
currentCallDepth: 0, maxCallDepth: 10, askTimeout: TimeSpan.FromSeconds(30),
|
||||
instanceName: Instance, logger: NullLogger.Instance);
|
||||
|
||||
[Fact]
|
||||
@@ -125,6 +128,64 @@ public class WriteBatchResetAndWaitTests : TestKit
|
||||
Assert.True(result.TimedOut);
|
||||
Assert.Equal(HandshakePhase.Response, result.Phase);
|
||||
Assert.False(result.ResetFailed);
|
||||
// The message names the attribute that did not answer and the bound it blew.
|
||||
Assert.Contains("MoveOutCompleteFlag", result.ErrorMessage);
|
||||
Assert.Contains("25", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reset wait that fails WITHOUT timing out (a predicate blew up in the waiter) surfaces the
|
||||
/// actor's own message verbatim rather than a synthesized one, and still writes nothing further.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ResetWaitError_PassesTheActorsMessageThrough()
|
||||
{
|
||||
var probe = CreateTestProbe();
|
||||
var ctx = CreateContext(probe.Ref);
|
||||
|
||||
var call = ctx.WriteBatchResetAndWait(
|
||||
Values, "MoveOutFlag", "True", "MoveOutCompleteFlag", "True", TimeSpan.FromSeconds(25),
|
||||
"MoveOutCompleteFlag", "False", TimeSpan.FromSeconds(10));
|
||||
|
||||
var reset = probe.ExpectMsg<SetStaticAttributeCommand>(TimeSpan.FromSeconds(5));
|
||||
probe.Reply(new SetStaticAttributeResponse(reset.CorrelationId, Instance, "MoveOutCompleteFlag", true, null, DateTimeOffset.UtcNow));
|
||||
var resetWait = probe.ExpectMsg<WaitForAttributeRequest>(TimeSpan.FromSeconds(5));
|
||||
probe.Reply(new WaitForAttributeResponse(
|
||||
resetWait.CorrelationId, Matched: false, null, null, TimedOut: false, ErrorMessage: "predicate threw"));
|
||||
|
||||
var result = await call;
|
||||
Assert.False(result.Matched);
|
||||
Assert.False(result.TimedOut);
|
||||
Assert.True(result.ResetFailed);
|
||||
Assert.Equal("predicate threw", result.ErrorMessage);
|
||||
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The batch leg keeps throwing after a successful reset — a device-rejected batch is a hard
|
||||
/// failure, not a <see cref="HandshakeResult"/> the script could mistake for a timeout.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task BatchFailureAfterASuccessfulReset_StillThrows()
|
||||
{
|
||||
var probe = CreateTestProbe();
|
||||
var ctx = CreateContext(probe.Ref);
|
||||
|
||||
var call = ctx.WriteBatchResetAndWait(
|
||||
Values, "MoveOutFlag", "True", "MoveOutCompleteFlag", "True", TimeSpan.FromSeconds(25),
|
||||
"MoveOutCompleteFlag", "False", TimeSpan.FromSeconds(10));
|
||||
|
||||
var reset = probe.ExpectMsg<SetStaticAttributeCommand>(TimeSpan.FromSeconds(5));
|
||||
probe.Reply(new SetStaticAttributeResponse(reset.CorrelationId, Instance, "MoveOutCompleteFlag", true, null, DateTimeOffset.UtcNow));
|
||||
var resetWait = probe.ExpectMsg<WaitForAttributeRequest>(TimeSpan.FromSeconds(5));
|
||||
probe.Reply(new WaitForAttributeResponse(resetWait.CorrelationId, Matched: true, false, "Good", TimedOut: false));
|
||||
var batch = probe.ExpectMsg<WriteAttributeBatchRequest>(TimeSpan.FromSeconds(5));
|
||||
probe.Reply(new WriteAttributeBatchResponse(batch.CorrelationId, false, "gateway rejected"));
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => call);
|
||||
Assert.Contains("gateway rejected", ex.Message);
|
||||
Assert.Contains("Batch write failed", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user