fix(dcl): deliver initial-read seed value after subscription registration
DataConnectionActor seeded a tag's initial value by Tell-ing TagValueReceived from HandleSubscribe's background task, which runs BEFORE HandleSubscribeCompleted registers the instance's tags in _subscriptionsByInstance. HandleTagValueReceived's fan-out then found no subscriber and dropped the value. A tag that soon gets a data-change notification recovers, but a STATIC tag (e.g. an idle MES field that never changes) was left Uncertain forever — the dropped seed was its only value. Seeds now ride back on SubscribeCompleted and are delivered after registration, reusing HandleTagValueReceived's generation guard, fan-out and quality accounting. +1 regression test (DCL026).
This commit was merged in pull request #2.
This commit is contained in:
@@ -546,6 +546,43 @@ public class DataConnectionActorTests : TestKit
|
||||
Assert.True(ack.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DCL026_StaticTagSeedValue_IsDeliveredAfterRegistration()
|
||||
{
|
||||
// Regression test for DataConnectionLayer-026. The initial-read seed value used to
|
||||
// be emitted (TagValueReceived) from HandleSubscribe's background task BEFORE
|
||||
// HandleSubscribeCompleted registered the instance's tags in
|
||||
// _subscriptionsByInstance. HandleTagValueReceived's fan-out then found no
|
||||
// subscriber for the tag and silently dropped the value. A tag that soon gets a
|
||||
// real data-change notification recovers, but a STATIC tag (subscribe succeeds,
|
||||
// callback never fires again — e.g. an idle MES field) was left Uncertain forever.
|
||||
// After the fix the seed rides on SubscribeCompleted and is delivered AFTER
|
||||
// registration, so the subscriber receives it.
|
||||
_mockAdapter.ConnectAsync(Arg.Any<IDictionary<string, string>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_mockAdapter.Status.Returns(ConnectionHealth.Connected);
|
||||
// Subscribe succeeds; the adapter never invokes the value callback (a static tag).
|
||||
_mockAdapter.SubscribeAsync(Arg.Any<string>(), Arg.Any<SubscriptionCallback>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_ => Task.FromResult("sub-static"));
|
||||
// The gateway returns a Good current value for the static tag.
|
||||
_mockAdapter.ReadAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new ReadResult(true,
|
||||
new TagValue("Left54321", QualityCode.Good, DateTimeOffset.UtcNow), null));
|
||||
|
||||
var actor = CreateConnectionActor("dcl026-static-seed");
|
||||
await Task.Delay(300); // reach Connected state
|
||||
|
||||
actor.Tell(new SubscribeTagsRequest(
|
||||
"c1", "inst1", "dcl026-static-seed",
|
||||
["MESReceiver_023.MoveInMesContainerNum"], DateTimeOffset.UtcNow));
|
||||
|
||||
// The seeded value must reach the subscriber (was dropped pre-fix).
|
||||
var update = FishForMessage<TagValueUpdate>(_ => true, TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("MESReceiver_023.MoveInMesContainerNum", update.TagPath);
|
||||
Assert.Equal(QualityCode.Good, update.Quality);
|
||||
Assert.Equal("Left54321", update.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DCL004_ConnectionLevelSubscribeFailure_TriggersReconnect_NotTagRetry()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user