diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs
index 88702dc8..e54da301 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Communication/NodeCommunicationActor.cs
@@ -1,5 +1,4 @@
using Akka.Actor;
-using Akka.Cluster.Tools.Client;
using Akka.Event;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
@@ -9,17 +8,18 @@ using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
///
-/// Node-side end of the central↔node command boundary (per-cluster mesh Phase 2), registered
-/// with the ClusterClientReceptionist at .
+/// Node-side end of the central↔node command boundary (per-cluster mesh Phase 2), living at
+/// — the path this node's NodeCommandService
+/// gRPC ingress hands every decoded command to.
///
///
///
-/// Registered per node, NOT as a cluster singleton — central's ClusterClient rotates
-/// across contact points and must find a live comm actor at whichever node answers.
+/// Registered per node, NOT as a cluster singleton — central dials each node's own
+/// command endpoint, so every node needs its own live comm actor.
///
///
/// Inbound commands are re-emitted on the node's , not on their
-/// DistributedPubSub topic. DPS is mesh-wide, and central SendToAlls to every
+/// DistributedPubSub topic. DPS is mesh-wide, and central delivers one copy to every
/// node's comm actor — so a DPS re-publish would deliver N copies of every command to every
/// subscriber, where N is the node count. The EventStream is node-local by construction,
/// which is exactly the needed semantics. It also avoids a registration handshake with
@@ -36,31 +36,23 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Communication;
///
public sealed class NodeCommunicationActor : ReceiveActor
{
- private readonly IActorRef? _centralClient;
private readonly IApplyAckSender? _ackSender;
private readonly ILoggingAdapter _log = Context.GetLogger();
/// Creates the props for this actor.
- ///
- /// ClusterClient dialling central's receptionist, or under
- /// MeshTransport:Mode = Dps, where acks still go out over DistributedPubSub from
- /// DriverHostActor and this actor never sees them — and under
- /// Mode = Grpc, where no ClusterClient is created at all.
- ///
///
/// The gRPC ack transport under MeshTransport:Mode = Grpc, or
- /// under Dps and ClusterClient.
+ /// under Dps, where acks still go out over DistributedPubSub from
+ /// DriverHostActor and this actor never sees them.
///
/// The props.
- public static Props Props(IActorRef? centralClient, IApplyAckSender? ackSender = null) =>
- Akka.Actor.Props.Create(() => new NodeCommunicationActor(centralClient, ackSender));
+ public static Props Props(IApplyAckSender? ackSender = null) =>
+ Akka.Actor.Props.Create(() => new NodeCommunicationActor(ackSender));
/// Initializes a new instance of the class.
- /// ClusterClient dialling central, or .
/// The gRPC ack transport, or .
- public NodeCommunicationActor(IActorRef? centralClient, IApplyAckSender? ackSender = null)
+ public NodeCommunicationActor(IApplyAckSender? ackSender = null)
{
- _centralClient = centralClient;
_ackSender = ackSender;
// Inbound from central. Each type is listed explicitly rather than caught by a ReceiveAny:
@@ -85,14 +77,10 @@ public sealed class NodeCommunicationActor : ReceiveActor
{
if (_ackSender is not null)
{
- // Fire-and-forget by construction, exactly as the ClusterClient Tell below is. The sender
- // never throws and never retries (see IApplyAckSender), so there is nothing to await and
- // nothing to pipe back: awaiting would stall this mailbox behind a network call to central,
- // and an Ask would invent a request/response the one-way contract does not have.
- //
- // Checked BEFORE the ClusterClient. The registration arm builds exactly one transport, so
- // both are never set — but were they, posting over both would deliver two acks for one
- // apply and duplicate central's work for no benefit.
+ // Fire-and-forget by construction. The sender never throws and never retries (see
+ // IApplyAckSender), so there is nothing to await and nothing to pipe back: awaiting would
+ // stall this mailbox behind a network call to central, and an Ask would invent a
+ // request/response the one-way contract does not have.
try
{
_ = _ackSender.SendAsync(ack);
@@ -115,22 +103,12 @@ public sealed class NodeCommunicationActor : ReceiveActor
return;
}
- if (_centralClient is null)
- {
- _log.Warning(
- "No central ack transport — dropping ApplyAck for {DeploymentId}. The ack is not "
- + "buffered and will not be retried, so the deployment will time out at central "
- + "naming this node even though it applied successfully. Check "
- + "MeshTransport:CentralContactPoints (ClusterClient mode) or "
- + "MeshTransport:CentralGrpcEndpoints (Grpc mode)",
- ack.DeploymentId);
- return;
- }
-
- // Send, not SendToAll: the deploy coordinator is a single singleton, and the central comm
- // actor forwards to its proxy. Its NodeId-keyed upsert absorbs a duplicate idempotently,
- // so single-target is defence in depth and a bound on wasted sends — mirroring the
- // corrected Grpc-branch rationale above.
- _centralClient.Tell(new ClusterClient.Send(MeshPaths.CentralCommunication, ack));
+ _log.Warning(
+ "No central ack transport — dropping ApplyAck for {DeploymentId}. The ack is not "
+ + "buffered and will not be retried, so the deployment will time out at central "
+ + "naming this node even though it applied successfully. Check "
+ + "MeshTransport:CentralGrpcEndpoints, and that MeshTransport:Mode is Grpc on a node "
+ + "whose central lives in another mesh",
+ ack.DeploymentId);
}
}
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs
index dd7c05ff..01b1a23b 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs
@@ -47,7 +47,6 @@ public static class ServiceCollectionExtensions
public const string OpcUaPublishActorName = "opcua-publish";
public const string PeerProbeSupervisorName = "peer-probe-supervisor";
public const string ContinuousHistorizationRecorderActorName = "continuous-historization-recorder";
- public const string CentralClusterClientName = "central-cluster-client";
///
/// Registers shared runtime services. Currently binds
@@ -101,8 +100,8 @@ public static class ServiceCollectionExtensions
// with no gate at all, and both nodes would write every sample twice with nothing to say so.
services.TryAddSingleton(sp => sp.GetRequiredService());
- // ClusterClient→gRPC migration Task 1B.2: the node→central ack transport under
- // MeshTransport:Mode = Grpc. Registered unconditionally but resolved only by the Grpc arm of
+ // The node→central ack transport under MeshTransport:Mode = Grpc (mesh gRPC command plane,
+ // Task 1B.2). Registered unconditionally but resolved only by the Grpc arm of
// WithOtOpcUaRuntimeActors — the mode is not knowable here (this runs before configuration is
// read into options), and a singleton nobody resolves is never constructed, so a Dps node
// opens no channel.
@@ -404,35 +403,15 @@ public static class ServiceCollectionExtensions
registry.Register(mux);
// Per-cluster mesh Phase 2: the node end of the central↔node command boundary. Spawned
- // BEFORE DriverHostActor because it is the host's ackRouter under ClusterClient mode.
- // It has no dependency of its own on the host — inbound commands travel via the node's
+ // BEFORE DriverHostActor because it is the host's ackRouter under Grpc mode. It has no
+ // dependency of its own on the host — inbound commands travel via the node's
// EventStream — so this ordering costs nothing.
var meshOptions = resolver.GetService>().Value;
- var useClusterClient = string.Equals(
- meshOptions.Mode, MeshTransportOptions.ModeClusterClient, StringComparison.OrdinalIgnoreCase);
var useGrpc = string.Equals(
meshOptions.Mode, MeshTransportOptions.ModeGrpc, StringComparison.OrdinalIgnoreCase);
- IActorRef? centralClient = null;
- if (useClusterClient)
- {
- // Contacts come from appsettings, not the database — the deliberate asymmetry:
- // central's node set changes as operators add and retire nodes, but central's own
- // address is part of the deployment. MeshTransportOptionsValidator has already
- // rejected an empty or malformed list at boot, so this cannot silently produce a
- // client with no contacts.
- var contacts = meshOptions.CentralContactPoints
- .Select(cp => ActorPath.Parse($"{cp}/system/receptionist"))
- .ToImmutableHashSet();
- centralClient = system.ActorOf(
- ClusterClient.Props(ClusterClientSettings.Create(system).WithInitialContacts(contacts)),
- CentralClusterClientName);
- }
-
- // ClusterClient→gRPC migration Task 1B.2. Deliberately EXCLUSIVE with the branch above —
- // under Grpc no ClusterClient is created at all, which is half the point of the migration
- // (it is the cross-WAN Akka association this program removes). The two are never both
- // handed to the actor, and it prefers the sender if they ever were.
+ // The node→central ack transport. Built ONLY under Grpc: under Dps the ack goes out on
+ // the deployment-acks topic from DriverHostActor and never reaches this actor at all.
IApplyAckSender? ackSender = null;
if (useGrpc)
{
@@ -451,7 +430,7 @@ public static class ServiceCollectionExtensions
}
var nodeComm = system.ActorOf(
- NodeCommunicationActor.Props(centralClient, ackSender),
+ NodeCommunicationActor.Props(ackSender),
MeshPaths.NodeCommunicationName);
// Registered with the receptionist in ALL modes: under Dps and Grpc nothing dials it, and
@@ -567,12 +546,11 @@ public static class ServiceCollectionExtensions
: null;
var driverHost = system.ActorOf(
- // ackRouter: under ClusterClient AND Grpc the node comm actor relays ApplyAcks across
- // the boundary (which transport it then uses is its own business — the ClusterClient
- // ref or the gRPC sender it was built with); under Dps it stays null and the host
- // publishes on the deployment-acks topic exactly as before. Grpc MUST be included
- // here: routed to DPS instead, a Grpc node's ack would never reach the sender at all
- // and every deployment to it would time out while it applied perfectly.
+ // ackRouter: under Grpc the node comm actor relays ApplyAcks across the mesh
+ // boundary through the gRPC sender it was built with; under Dps it stays null and
+ // the host publishes on the deployment-acks topic exactly as before. Routed to DPS
+ // instead, a Grpc node's ack would never reach the sender at all and every
+ // deployment to it would time out while it applied perfectly.
// dbFactory is nullable (Phase 4): a driver-only node passes null here — ConfigDb is
// gated on hasAdmin and driver-only nodes are forced to FetchAndCache. DriverHostActor
// guards every ConfigDb-touching path on it (UpsertNodeDeploymentState no-ops; central
@@ -580,7 +558,7 @@ public static class ServiceCollectionExtensions
// alarmStateStore (there is no ConfigDb-backed EF fallback; the host skips the alarm
// host when no store was wired).
DriverHostActor.Props(dbFactory, roleInfo.LocalNode,
- ackRouter: useClusterClient || useGrpc ? nodeComm : null,
+ ackRouter: useGrpc ? nodeComm : null,
driverFactory: driverFactory, localRoles: roleInfo.LocalRoles,
dependencyMux: mux,
opcUaPublishActor: publishActor,
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs
index c9c66be1..aaa06b3c 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/EventStreamCommandDeliveryTests.cs
@@ -50,7 +50,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase
var driverHost = Sys.ActorOf(
DriverHostActor.Props(NewInMemoryDbFactory(), new NodeId("127.0.0.1:4053"), ackRouter: ackRouter.Ref),
"driver-host");
- var comm = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null), "node-communication");
+ var comm = Sys.ActorOf(NodeCommunicationActor.Props(), "node-communication");
// Warm-up: a direct Tell whose ack proves PreStart has completed, and with it the
// EventStream subscription. Without this the relay below races the subscription.
@@ -141,7 +141,7 @@ akka {
ScriptedAlarmHostActor.Props(
CreateTestProbe().Ref, CreateTestProbe().Ref, upstream, engine, new NodeId("node-A")),
"scripted-alarm-host");
- var comm = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null), "node-communication");
+ var comm = Sys.ActorOf(NodeCommunicationActor.Props(), "node-communication");
var cmd = new AlarmCommand("alarm-unowned", "Acknowledge", "alice", "c", null);
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs
index 473d2d02..e1bb1f9d 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Communication/NodeCommunicationActorTests.cs
@@ -1,5 +1,4 @@
using Akka.Actor;
-using Akka.Cluster.Tools.Client;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
@@ -24,7 +23,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
{
var subscriber = CreateTestProbe();
Sys.EventStream.Subscribe(subscriber.Ref, typeof(DispatchDeployment));
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props());
var dispatch = NewDispatch();
actor.Tell(dispatch);
@@ -40,7 +39,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
// subscriber. One publish per inbound command is the whole point.
var subscriber = CreateTestProbe();
Sys.EventStream.Subscribe(subscriber.Ref, typeof(DispatchDeployment));
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props());
actor.Tell(NewDispatch());
@@ -53,7 +52,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
{
var subscriber = CreateTestProbe();
Sys.EventStream.Subscribe(subscriber.Ref, typeof(RestartDriver));
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props());
var msg = new RestartDriver("C1", "driver-1", "alice", Guid.NewGuid());
actor.Tell(msg);
@@ -66,7 +65,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
{
var subscriber = CreateTestProbe();
Sys.EventStream.Subscribe(subscriber.Ref, typeof(ReconnectDriver));
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props());
var msg = new ReconnectDriver("C1", "driver-1", "alice", Guid.NewGuid());
actor.Tell(msg);
@@ -82,7 +81,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
// leg possible without a registration handshake.
var subscriber = CreateTestProbe();
Sys.EventStream.Subscribe(subscriber.Ref, typeof(AlarmCommand));
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props());
var msg = new AlarmCommand("alarm-1", "Acknowledge", "alice", "comment", null);
actor.Tell(msg);
@@ -91,32 +90,11 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
}
[Fact]
- public void ApplyAck_goes_to_central_as_a_Send_not_a_SendToAll()
+ public void An_ack_with_no_ack_sender_is_dropped_with_a_warning()
{
- // Send, not SendToAll: the deploy coordinator is one singleton behind the central comm
- // actor's proxy. The coordinator's NodeId-keyed upsert absorbs a duplicate idempotently,
- // so the single-target rule is defence in depth and a bound on wasted work, not a
- // correctness requirement.
- var client = CreateTestProbe();
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(client.Ref));
-
- var ack = new ApplyAck(
- new DeploymentId(Guid.NewGuid()),
- new NodeId("host-a:4053"),
- ApplyAckOutcome.Applied,
- null,
- new CorrelationId(Guid.NewGuid()));
- actor.Tell(ack);
-
- var sent = client.ExpectMsg(TimeSpan.FromSeconds(5));
- sent.Path.ShouldBe(MeshPaths.CentralCommunication);
- sent.Message.ShouldBe(ack);
- }
-
- [Fact]
- public void An_ack_with_no_central_client_is_dropped_with_a_warning()
- {
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null));
+ // Dps: DriverHostActor publishes the ack on the deployment-acks topic and never routes it
+ // here, so an ack arriving with no sender wired is a mis-wiring and must say so.
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props());
var ack = new ApplyAck(
new DeploymentId(Guid.NewGuid()),
@@ -129,7 +107,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
TimeSpan.FromSeconds(5), () => actor.Tell(ack));
}
- // ---- Mode=Grpc (ClusterClient→gRPC migration Task 1B.2) -------------------------------------
+ // ---- Mode=Grpc (the cross-mesh ack leg) -----------------------------------------------------
private static ApplyAck NewAck() => new(
new DeploymentId(Guid.NewGuid()),
@@ -141,13 +119,12 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
[Fact]
public void Under_Grpc_the_ack_goes_to_the_sender_exactly_once()
{
- // The gRPC counterpart of the ClusterClient.Send fact above, and it carries the same
- // single-count obligation: one post per node per dispatch, because both central endpoints
- // route to the SAME coordinator singleton proxy. Where the ack goes among the endpoints is the
- // sender's business (GrpcApplyAckSenderTests); what this pins is that the actor hands it over
- // once and does not also fan it out itself.
+ // One post per node per dispatch, because both central endpoints route to the SAME
+ // coordinator singleton proxy. Where the ack goes among the endpoints is the sender's
+ // business (GrpcApplyAckSenderTests); what this pins is that the actor hands it over once
+ // and does not also fan it out itself.
var sender = new RecordingAckSender();
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null, ackSender: sender));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props(sender));
var ack = NewAck();
actor.Tell(ack);
@@ -158,37 +135,6 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
sender.Sent.Count.ShouldBe(1);
}
- [Fact]
- public void A_sender_wins_over_a_cluster_client_and_the_ack_is_not_sent_twice()
- {
- // Defence in depth. The registration arm builds exactly one transport — under Grpc no
- // ClusterClient is created at all — and if both were ever present, posting over both would
- // deliver two acks for one apply. The coordinator would absorb the duplicate (NodeId-keyed
- // upsert), so this pins tidiness and bounded work, not seal correctness.
- var client = CreateTestProbe();
- var sender = new RecordingAckSender();
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(client.Ref, sender));
-
- actor.Tell(NewAck());
-
- AwaitAssert(() => sender.Sent.Count.ShouldBe(1), TimeSpan.FromSeconds(5));
- client.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
- }
-
- [Fact]
- public void The_ClusterClient_branch_never_touches_the_sender()
- {
- // Null-safe by construction: under Dps and ClusterClient the sender is not built at all, so the
- // ApplyAck path must not depend on one existing.
- var client = CreateTestProbe();
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(client.Ref));
-
- var ack = NewAck();
- actor.Tell(ack);
-
- client.ExpectMsg(TimeSpan.FromSeconds(5)).Message.ShouldBe(ack);
- }
-
[Fact]
public void Inbound_commands_are_unaffected_by_the_presence_of_a_sender()
{
@@ -197,7 +143,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
var subscriber = CreateTestProbe();
Sys.EventStream.Subscribe(subscriber.Ref, typeof(DispatchDeployment));
var sender = new RecordingAckSender();
- var actor = Sys.ActorOf(NodeCommunicationActor.Props(centralClient: null, ackSender: sender));
+ var actor = Sys.ActorOf(NodeCommunicationActor.Props(sender));
var dispatch = NewDispatch();
actor.Tell(dispatch);
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs
index f649d30d..0b8965c7 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/ServiceCollectionExtensionsTests.cs
@@ -127,23 +127,22 @@ public sealed class ServiceCollectionExtensionsTests
///
/// Under MeshTransport:Mode = Grpc an ApplyAck produced by the driver host reaches the
- /// registered , and no ClusterClient actor exists
- /// (ClusterClient→gRPC migration Task 1B.2).
+ /// registered .
///
///
/// This is the fact that pins the ackRouter gate in WithOtOpcUaRuntimeActors
- /// (useClusterClient || useGrpc ? nodeComm : null). Narrowed back to ClusterClient-only
- /// that gate still compiles and every unit test still passes, while every ack on a Grpc node
- /// goes out on the deployment-acks DPS topic instead — where, in the split-mesh topology
- /// Grpc exists to serve, central is not subscribed. The whole transport would be dead and
- /// nothing below this level of the stack could tell.
+ /// (useGrpc ? nodeComm : null). Drop the useGrpc arm and that gate still compiles
+ /// and every unit test still passes, while every ack on a Grpc node goes out on the
+ /// deployment-acks DPS topic instead — where, in the split-mesh topology Grpc exists to
+ /// serve, central is not subscribed. The whole transport would be dead and nothing below this
+ /// level of the stack could tell.
///
/// The dispatch names a deployment that does not exist in the in-memory ConfigDb, so the apply
/// fails and the host emits a Failed ack — deterministic, synchronous, and it exercises the
/// real SendAck path rather than poking the router directly.
///
[Fact]
- public async Task Under_Grpc_a_driver_host_ack_reaches_the_ack_sender_and_no_ClusterClient_exists()
+ public async Task Under_Grpc_a_driver_host_ack_reaches_the_ack_sender()
{
var sender = new RecordingAckSender();
using var host = BuildRuntimeActorHost(services =>
@@ -160,7 +159,6 @@ public sealed class ServiceCollectionExtensionsTests
await host.StartAsync();
try
{
- var system = host.Services.GetRequiredService();
var driverHost = host.Services.GetRequiredService>();
driverHost.ActorRef.Tell(new DispatchDeployment(
@@ -168,18 +166,14 @@ public sealed class ServiceCollectionExtensionsTests
new RevisionHash("rev-that-does-not-exist"),
new CorrelationId(Guid.NewGuid())));
- // The ack travelled DriverHostActor -> NodeCommunicationActor -> IApplyAckSender. Under a
- // ClusterClient-only gate it would have gone to DPS and this never completes.
+ // The ack travelled DriverHostActor -> NodeCommunicationActor -> IApplyAckSender. With the
+ // useGrpc arm dropped from the ackRouter gate it would have gone to DPS instead and this
+ // never completes.
var deadline = DateTime.UtcNow.AddSeconds(15);
while (sender.Sent.Count == 0 && DateTime.UtcNow < deadline)
await Task.Delay(50);
sender.Sent.Count.ShouldBe(1);
-
- // And the Akka cross-mesh association is genuinely gone, not merely unused.
- await Should.ThrowAsync(() => system
- .ActorSelection($"/user/{ServiceCollectionExtensions.CentralClusterClientName}")
- .ResolveOne(TimeSpan.FromMilliseconds(500)));
}
finally
{