feat: add JetStream cluster replication and leaf node solicited reconnect

Add JetStream stream/consumer config and data replication across cluster
peers via $JS.INTERNAL.* subjects with BroadcastRoutedMessageAsync (sends
to all peers, bypassing pool routing). Capture routed data messages into
local JetStream stores in DeliverRemoteMessage. Fix leaf node solicited
reconnect by re-launching the retry loop in WatchConnectionAsync after
disconnect.

Unskips 4 of 5 E2E cluster tests (LeaderDies_NewLeaderElected,
R3Stream_NodeDies_PublishContinues, Consumer_NodeDies_PullContinuesOnSurvivor,
Leaf_HubRestart_LeafReconnects). The 5th (LeaderRestart_RejoinsAsFollower)
requires RAFT log catchup which is a separate feature.
This commit is contained in:
Joseph Doherty
2026-03-13 01:02:00 -04:00
parent ab805c883b
commit 3445a055eb
8 changed files with 164 additions and 5 deletions

View File

@@ -74,7 +74,7 @@ public class RaftConsensusTests(JetStreamClusterFixture fixture) : IClassFixture
}
// Go ref: server/raft_test.go TestNRGStepDown
[Fact(Skip = "JetStream RAFT leader re-election not yet implemented — stream is unavailable on surviving nodes after leader dies")]
[Fact]
[SlopwatchSuppress("SW001", "JetStream RAFT leader re-election is not yet implemented in the .NET server — stream data is local to the publishing node and cannot fail over")]
public async Task LeaderDies_NewLeaderElected()
{
@@ -151,7 +151,7 @@ public class RaftConsensusTests(JetStreamClusterFixture fixture) : IClassFixture
}
// Go ref: server/raft_test.go TestNRGCatchup
[Fact(Skip = "JetStream RAFT catchup not yet implemented — restarted node cannot catch up via RAFT log")]
[Fact(Skip = "RAFT log catchup not yet implemented — a restarted node cannot recover messages published to peers during its downtime")]
[SlopwatchSuppress("SW001", "JetStream RAFT log catchup is not yet implemented in the .NET server — a restarted node has no mechanism to receive missed messages from peers")]
public async Task LeaderRestart_RejoinsAsFollower()
{