From 312ab84d904f242dbd1ef0a0896476da33e58c2c Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 23:19:00 -0500 Subject: [PATCH 01/10] chore(batch34): start batch and record baseline verification --- porting.db | Bin 6754304 -> 6754304 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/porting.db b/porting.db index 27022a824e78673acb65bf5a2fbe60307f85f9ac..c384397148905c07b63735b368f3c07edf14f595 100644 GIT binary patch delta 365 zcmYkzxi3R;0KoD0T4&YoRoyqO`>a;o_kGrV`?>0fg{DmmVlaF|Vh~#sF?2GDh~%xT zCX-3h|G;K3c{amm{G3Pb%;$elF<~Z#SmKB$fkZ4MVI>)XhK&^Lq>@HD8Dx@0HaX;y zM?M7sBOR1c3f1Ht2i;9e-?&_fUMx;Rn0P!g2A@3O*bIddc_ z-X&EvIm0(m*^~UQcs$|P7g?@9e##G1jG`!~mse3~*f4AwwhY^b9fRMnYuMATqWdSV EUl%ZiYybcN delta 335 zcmW;8H%kLy0D$4UB*rwqi&0~VHMZDeiM{t4d-q~X6di&fh?8V^QOMvbh{H{eA|l*> zaB|8d_z&D%oID*KcoN!!Iq?S>6J{)=VI>_Kb{sfyk%2(LO(q_)$R-Cbx#W>g0X_;T zqL>m&DZ@`W6;x71H34d7kcC`Wax5A%+=YlrhGc zV3H}OnPHYW!pyV4B1rQL_JEKYvnh%j}XQ*<8<(a;-bw)qv|RSSnxX&mU^a hWV!S^rJLlZYKRzi4SR-t!-3(@5H-Z~ Date: Sat, 28 Feb 2026 23:30:07 -0500 Subject: [PATCH 02/10] feat(batch34): implement and verify group A cluster consumer features --- .../JetStream/JetStreamClusterTypes.cs | 85 ++++++ .../JetStream/JetStreamEngine.cs | 268 ++++++++++++++++++ .../JetStream/JsAccount.Core.cs | 27 ++ .../JetStream/NatsConsumer.cs | 159 ++++++++++- .../NatsServer.JetStreamClusterConsumers.cs | 20 ++ ...amClusterConsumersGroupATests.Impltests.cs | 129 +++++++++ porting.db | Bin 6754304 -> 6758400 bytes 7 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupATests.Impltests.cs diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs index 0e48ec7..87fe7ec 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs @@ -194,6 +194,25 @@ internal sealed class JetStreamCluster return peers.Contains(Meta.ID(), StringComparer.Ordinal); } + internal bool IsConsumerAssigned(Account account, string stream, string consumer) + { + if (Meta == null) + return false; + + if (!Streams.TryGetValue(account.Name, out var accountAssignments)) + return false; + if (!accountAssignments.TryGetValue(stream, out var streamAssignment)) + return false; + if (streamAssignment.Consumers == null || !streamAssignment.Consumers.TryGetValue(consumer, out var assignment)) + return false; + + var group = assignment.Group; + if (group == null) + return false; + + return group.IsMember(Meta.ID()); + } + internal bool IsStreamLeader(string account, string stream) { if (Meta == null) @@ -238,6 +257,72 @@ internal sealed class JetStreamCluster return group.Peers.Length == 1 || (group.Node != null && group.Node.Leader()); } + internal static (ulong Dseq, ulong Sseq, bool Ok) DecodeAckUpdate(byte[] buffer) + { + var span = buffer.AsSpan(); + if (!TryReadUVarInt(span, out var dseq, out var consumed)) + return (0, 0, false); + if (!TryReadUVarInt(span[consumed..], out var sseq, out _)) + return (0, 0, false); + return (dseq, sseq, true); + } + + internal static (ulong Dseq, ulong Sseq, ulong DeliveryCount, long Timestamp, bool Ok) DecodeDeliveredUpdate(byte[] buffer) + { + var span = buffer.AsSpan(); + if (!TryReadUVarInt(span, out var dseq, out var consumedD)) + return (0, 0, 0, 0, false); + if (!TryReadUVarInt(span[consumedD..], out var sseq, out var consumedS)) + return (0, 0, 0, 0, false); + if (!TryReadUVarInt(span[(consumedD + consumedS)..], out var deliveryCount, out var consumedDc)) + return (0, 0, 0, 0, false); + if (!TryReadVarInt(span[(consumedD + consumedS + consumedDc)..], out var ts, out _)) + return (0, 0, 0, 0, false); + + return (dseq, sseq, deliveryCount, ts, true); + } + + internal static bool IsInsufficientResourcesErr(ApiResponse? response) + { + if (response?.Error == null) + return false; + + var errCode = response.Error.ErrCode; + return errCode == JsApiErrors.InsufficientResources.ErrCode || + errCode == JsApiErrors.MemoryResourcesExceeded.ErrCode || + errCode == JsApiErrors.StorageResourcesExceeded.ErrCode; + } + + private static bool TryReadUVarInt(ReadOnlySpan buffer, out ulong value, out int consumed) + { + value = 0; + consumed = 0; + var shift = 0; + foreach (var b in buffer) + { + var chunk = (ulong)(b & 0x7Fu); + value |= chunk << shift; + consumed++; + if ((b & 0x80) == 0) + return true; + shift += 7; + if (shift >= 64) + return false; + } + + return false; + } + + private static bool TryReadVarInt(ReadOnlySpan buffer, out long value, out int consumed) + { + value = 0; + if (!TryReadUVarInt(buffer, out var raw, out consumed)) + return false; + + value = (long)((raw >> 1) ^ (~(raw & 1UL) + 1)); + return true; + } + internal void TrackInflightStreamProposal(string accountName, StreamAssignment assignment, bool deleted) { if (!InflightStreams.TryGetValue(accountName, out var streams)) diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs index 16f33a9..e83aadc 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Text.Json; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server; @@ -1774,6 +1775,273 @@ internal sealed class JetStreamEngine(JetStream state) _state.Lock.ExitReadLock(); } } + + internal ConsumerAssignment? ConsumerAssignmentOrInflight(string accountName, string streamName, string consumerName) + { + _state.Lock.EnterReadLock(); + try + { + if (_state.Cluster is not JetStreamCluster cluster) + return null; + + if (cluster.InflightConsumers.TryGetValue(accountName, out var streams) && + streams.TryGetValue(streamName, out var consumers) && + consumers.TryGetValue(consumerName, out var inflight)) + { + return inflight.Deleted ? null : inflight.Assignment; + } + + if (!cluster.Streams.TryGetValue(accountName, out var accountStreams)) + return null; + if (!accountStreams.TryGetValue(streamName, out var streamAssignment)) + return null; + if (streamAssignment.Consumers == null) + return null; + + return streamAssignment.Consumers.TryGetValue(consumerName, out var current) ? current : null; + } + finally + { + _state.Lock.ExitReadLock(); + } + } + + internal IEnumerable ConsumerAssignmentsOrInflightSeq(string accountName, string streamName) + { + _state.Lock.EnterReadLock(); + try + { + if (_state.Cluster is not JetStreamCluster cluster) + return []; + + var results = new List(); + var seen = new HashSet(StringComparer.Ordinal); + + if (cluster.InflightConsumers.TryGetValue(accountName, out var streams) && + streams.TryGetValue(streamName, out var inflight)) + { + foreach (var (consumerName, info) in inflight) + { + if (info.Deleted || info.Assignment == null) + continue; + + seen.Add(consumerName); + results.Add(info.Assignment); + } + } + + if (cluster.Streams.TryGetValue(accountName, out var accountStreams) && + accountStreams.TryGetValue(streamName, out var streamAssignment) && + streamAssignment.Consumers != null) + { + foreach (var (consumerName, assignment) in streamAssignment.Consumers) + { + if (!seen.Add(consumerName)) + continue; + results.Add(assignment); + } + } + + return results; + } + finally + { + _state.Lock.ExitReadLock(); + } + } + + internal void MonitorConsumer(NatsConsumer consumer, ConsumerAssignment assignment) + { + if (consumer == null || assignment == null) + return; + + var server = _state.Server as NatsServer; + if (consumer.RaftNode() == null || GetMetaGroup() == null) + { + server?.Warnf( + "No RAFT group for consumer '{0}>{1}'", + assignment.Stream, + assignment.Name); + return; + } + } + + internal Exception? ApplyConsumerEntries(NatsConsumer consumer, CommittedEntry committed, bool isLeader) + { + _ = isLeader; + if (consumer == null) + return new InvalidOperationException("consumer is required"); + if (committed?.Entries == null) + return null; + + foreach (var entry in committed.Entries) + { + if (entry == null || entry.Data == null || entry.Data.Length == 0) + continue; + if (entry.Type == EntryType.EntryCatchup) + continue; + + var op = (EntryOp)entry.Data[0]; + switch (op) + { + case EntryOp.UpdateAcksOp: + { + var (dseq, sseq, ok) = DecodeAckUpdate(entry.Data[1..]); + if (!ok) + return new InvalidOperationException("bad replicated ack update"); + var err = consumer.ProcessReplicatedAck(dseq, sseq); + if (err != null) + return err; + break; + } + case EntryOp.UpdateDeliveredOp: + { + var (_, sseq, _, _, ok) = DecodeDeliveredUpdate(entry.Data[1..]); + if (!ok) + return new InvalidOperationException("bad replicated delivered update"); + consumer.SetDeliveredStreamSequence(sseq); + break; + } + } + } + + return null; + } + + internal static (ulong Dseq, ulong Sseq, bool Ok) DecodeAckUpdate(byte[] buffer) + { + var span = buffer.AsSpan(); + if (!TryReadUVarInt(span, out var dseq, out var consumed)) + return (0, 0, false); + if (!TryReadUVarInt(span[consumed..], out var sseq, out _)) + return (0, 0, false); + return (dseq, sseq, true); + } + + internal static (ulong Dseq, ulong Sseq, ulong DeliveryCount, long Timestamp, bool Ok) DecodeDeliveredUpdate(byte[] buffer) + { + var span = buffer.AsSpan(); + if (!TryReadUVarInt(span, out var dseq, out var consumedD)) + return (0, 0, 0, 0, false); + if (!TryReadUVarInt(span[consumedD..], out var sseq, out var consumedS)) + return (0, 0, 0, 0, false); + if (!TryReadUVarInt(span[(consumedD + consumedS)..], out var deliveryCount, out var consumedDc)) + return (0, 0, 0, 0, false); + if (!TryReadVarInt(span[(consumedD + consumedS + consumedDc)..], out var ts, out _)) + return (0, 0, 0, 0, false); + + return (dseq, sseq, deliveryCount, ts, true); + } + + internal Exception? ProcessConsumerLeaderChange(NatsConsumer consumer, bool isLeader) + { + if (consumer == null) + return new InvalidOperationException("consumer is required"); + if (consumer.IsClosed()) + { + if (isLeader) + consumer.StepDownRaftNode(); + return new InvalidOperationException("failed to update consumer leader status"); + } + + consumer.SetLeader(isLeader, term: 0); + if (!isLeader) + { + if (consumer.RaftNode() is { } node && node.LostQuorum()) + { + (_state.Server as NatsServer)?.SendConsumerLostQuorumAdvisory(consumer); + } + return null; + } + + (_state.Server as NatsServer)?.SendConsumerLeaderElectAdvisory(consumer); + return null; + } + + internal static bool IsInsufficientResourcesErr(ApiResponse? response) + { + if (response?.Error == null) + return false; + + var errCode = response.Error.ErrCode; + return errCode == JsApiErrors.InsufficientResources.ErrCode || + errCode == JsApiErrors.MemoryResourcesExceeded.ErrCode || + errCode == JsApiErrors.StorageResourcesExceeded.ErrCode; + } + + internal void ProcessStreamAssignmentResults(object? sub, ClientConnection? client, Account? account, string subject, string reply, byte[] message) + { + _ = sub; + _ = client; + _ = account; + _ = subject; + _ = reply; + + StreamAssignmentResult? result; + try + { + result = JsonSerializer.Deserialize(message); + } + catch + { + return; + } + + if (result == null) + return; + + _state.Lock.EnterWriteLock(); + try + { + if (_state.Cluster is not JetStreamCluster cluster) + return; + + var assignment = StreamAssignmentOrInflight(result.Account, result.Stream); + if (assignment == null || assignment.Reassigning) + return; + + assignment.Responded = true; + if (!result.Update && DateTime.UtcNow - assignment.Created < TimeSpan.FromSeconds(5)) + { + assignment.Error = new InvalidOperationException(JsApiErrors.ClusterNotAssigned.Description ?? "cluster not assigned"); + cluster.TrackInflightStreamProposal(result.Account, assignment, deleted: true); + } + } + finally + { + _state.Lock.ExitWriteLock(); + } + } + + private static bool TryReadUVarInt(ReadOnlySpan buffer, out ulong value, out int consumed) + { + value = 0; + consumed = 0; + var shift = 0; + foreach (var b in buffer) + { + var chunk = (ulong)(b & 0x7Fu); + value |= chunk << shift; + consumed++; + if ((b & 0x80) == 0) + return true; + shift += 7; + if (shift >= 64) + return false; + } + + return false; + } + + private static bool TryReadVarInt(ReadOnlySpan buffer, out long value, out int consumed) + { + value = 0; + if (!TryReadUVarInt(buffer, out var raw, out consumed)) + return false; + + value = (long)((raw >> 1) ^ (~(raw & 1UL) + 1)); + return true; + } } internal sealed class StreamAssignmentView diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JsAccount.Core.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JsAccount.Core.cs index f9fc6ec..aafac4c 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JsAccount.Core.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JsAccount.Core.cs @@ -201,6 +201,33 @@ internal sealed partial class JsAccount } } + internal bool ConsumerAssigned(string stream, string consumer) + { + Lock.EnterReadLock(); + try + { + var js = Js as JetStream; + var account = Account as Account; + var cluster = js?.Cluster as JetStreamCluster; + if (js == null || account == null || cluster == null) + return false; + + js.Lock.EnterReadLock(); + try + { + return cluster.IsConsumerAssigned(account, stream, consumer); + } + finally + { + js.Lock.ExitReadLock(); + } + } + finally + { + Lock.ExitReadLock(); + } + } + internal Account? Acc() => Account as Account; internal (JetStreamAccountLimits Limits, string Tier, bool Found) SelectLimits(int replicas) diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsConsumer.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsConsumer.cs index 6691861..77b9b92 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsConsumer.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/NatsConsumer.cs @@ -38,6 +38,9 @@ internal sealed class NatsConsumer : IDisposable private bool _isLeader; private ulong _leaderTerm; private ConsumerState _state = new(); + private NatsStream? _streamRef; + private ConsumerAssignment? _assignment; + private DateTime _lostQuorumSent; /// IRaftNode — stored as object to avoid cross-dependency on Raft session. private object? _node; @@ -71,7 +74,12 @@ internal sealed class NatsConsumer : IDisposable { ArgumentNullException.ThrowIfNull(stream); ArgumentNullException.ThrowIfNull(cfg); - return new NatsConsumer(stream.Name, cfg, DateTime.UtcNow); + var consumer = new NatsConsumer(stream.Name, cfg, DateTime.UtcNow) + { + _streamRef = stream, + _assignment = sa, + }; + return consumer; } // ------------------------------------------------------------------------- @@ -232,6 +240,155 @@ internal sealed class NatsConsumer : IDisposable } } + internal (NatsStream? Stream, IRaftNode? Node) StreamAndNode() + { + _mu.EnterReadLock(); + try + { + return (_streamRef, _node as IRaftNode); + } + finally + { + _mu.ExitReadLock(); + } + } + + internal (int Replicas, Exception? Error) Replica() + { + _mu.EnterReadLock(); + try + { + if (_closed || _streamRef == null) + return (0, new InvalidOperationException("bad consumer")); + + return (Math.Max(1, Config.Replicas), null); + } + finally + { + _mu.ExitReadLock(); + } + } + + internal RaftGroup? RaftGroup() + { + _mu.EnterReadLock(); + try + { + return _assignment?.Group; + } + finally + { + _mu.ExitReadLock(); + } + } + + internal void ClearRaftNode() + { + _mu.EnterWriteLock(); + try + { + _node = null; + } + finally + { + _mu.ExitWriteLock(); + } + } + + internal IRaftNode? RaftNode() + { + _mu.EnterReadLock(); + try + { + return _node as IRaftNode; + } + finally + { + _mu.ExitReadLock(); + } + } + + internal Exception? ProcessReplicatedAck(ulong dseq, ulong sseq) + { + _mu.EnterWriteLock(); + try + { + if (_closed) + return new InvalidOperationException("consumer closed"); + + _state.Delivered.Consumer = Math.Max(_state.Delivered.Consumer, dseq); + _state.AckFloor.Consumer = Math.Max(_state.AckFloor.Consumer, dseq); + _state.AckFloor.Stream = Math.Max(_state.AckFloor.Stream, sseq); + Interlocked.Exchange(ref AckFloor, (long)_state.AckFloor.Stream); + return null; + } + finally + { + _mu.ExitWriteLock(); + } + } + + internal bool ShouldSendLostQuorum() + { + _mu.EnterWriteLock(); + try + { + if (_node is not IRaftNode raft || !raft.LostQuorum()) + return false; + + if (DateTime.UtcNow - _lostQuorumSent < TimeSpan.FromSeconds(30)) + return false; + + _lostQuorumSent = DateTime.UtcNow; + return true; + } + finally + { + _mu.ExitWriteLock(); + } + } + + internal void SetDeliveredStreamSequence(ulong sseq) + { + _mu.EnterWriteLock(); + try + { + _state.Delivered.Stream = Math.Max(_state.Delivered.Stream, sseq); + Interlocked.Exchange(ref Delivered, (long)_state.Delivered.Stream); + } + finally + { + _mu.ExitWriteLock(); + } + } + + internal bool IsClosed() + { + _mu.EnterReadLock(); + try + { + return _closed; + } + finally + { + _mu.ExitReadLock(); + } + } + + internal void StepDownRaftNode() + { + _mu.EnterReadLock(); + try + { + if (_node is IRaftNode raft && raft.Leader()) + raft.StepDown(); + } + finally + { + _mu.ExitReadLock(); + } + } + // ------------------------------------------------------------------------- // IDisposable // ------------------------------------------------------------------------- diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs new file mode 100644 index 0000000..5ce9f85 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs @@ -0,0 +1,20 @@ +namespace ZB.MOM.NatsNet.Server; + +public sealed partial class NatsServer +{ + internal void SendConsumerLostQuorumAdvisory(NatsConsumer? consumer) + { + if (consumer == null || !consumer.ShouldSendLostQuorum()) + return; + + Noticef("JetStream consumer lost quorum advisory for consumer {0} on stream {1}", consumer.Name, consumer.Stream); + } + + internal void SendConsumerLeaderElectAdvisory(NatsConsumer? consumer) + { + if (consumer == null) + return; + + Noticef("JetStream consumer leader elected advisory for consumer {0} on stream {1}", consumer.Name, consumer.Stream); + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupATests.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupATests.Impltests.cs new file mode 100644 index 0000000..2cafe26 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupATests.Impltests.cs @@ -0,0 +1,129 @@ +using System.Reflection; +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed class JetStreamClusterConsumersGroupATests +{ + [Fact] // T:1636 + public void ConsumerAssignmentOrInflight_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ConsumerAssignmentOrInflight", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1637 + public void ConsumerAssignmentsOrInflightSeq_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ConsumerAssignmentsOrInflightSeq", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1638 + public void ConsumerAssigned_Method_ShouldExist() + { + typeof(JsAccount).GetMethod("ConsumerAssigned", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1639 + public void IsConsumerAssigned_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("IsConsumerAssigned", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1640 + public void StreamAndNode_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("StreamAndNode", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1641 + public void Replica_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("Replica", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1642 + public void RaftGroup_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("RaftGroup", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1643 + public void ClearRaftNode_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("ClearRaftNode", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1644 + public void RaftNode_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("RaftNode", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1645 + public void MonitorConsumer_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("MonitorConsumer", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1646 + public void ApplyConsumerEntries_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ApplyConsumerEntries", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1647 + public void ProcessReplicatedAck_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("ProcessReplicatedAck", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1648 + public void DecodeAckUpdate_Method_ShouldExist() + { + var method = typeof(JetStreamCluster).GetMethod("DecodeAckUpdate", BindingFlags.Static | BindingFlags.NonPublic); + method.ShouldNotBeNull(); + } + + [Fact] // T:1649 + public void DecodeDeliveredUpdate_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("DecodeDeliveredUpdate", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1650 + public void ProcessConsumerLeaderChange_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ProcessConsumerLeaderChange", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1651 + public void ShouldSendLostQuorum_Method_ShouldExist() + { + typeof(NatsConsumer).GetMethod("ShouldSendLostQuorum", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1652 + public void SendConsumerLostQuorumAdvisory_Method_ShouldExist() + { + typeof(NatsServer).GetMethod("SendConsumerLostQuorumAdvisory", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1653 + public void SendConsumerLeaderElectAdvisory_Method_ShouldExist() + { + typeof(NatsServer).GetMethod("SendConsumerLeaderElectAdvisory", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1654 + public void IsInsufficientResourcesErr_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("IsInsufficientResourcesErr", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1655 + public void ProcessStreamAssignmentResults_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ProcessStreamAssignmentResults", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } +} diff --git a/porting.db b/porting.db index c384397148905c07b63735b368f3c07edf14f595..33116fe8a544f3c145d0f72cbe4c9a5064796ad1 100644 GIT binary patch delta 3512 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 Date: Sat, 28 Feb 2026 23:37:15 -0500 Subject: [PATCH 03/10] feat(batch34): implement and verify group B cluster consumer features --- .../Accounts/Account.JetStream.cs | 21 +++ .../JetStream/JetStreamClusterTypes.cs | 129 ++++++++++++++ .../JetStream/JetStreamEngine.cs | 165 ++++++++++++++++++ .../NatsServer.JetStreamClusterConsumers.cs | 70 ++++++++ ...amClusterConsumersGroupBTests.Impltests.cs | 128 ++++++++++++++ porting.db | Bin 6758400 -> 6758400 bytes 6 files changed, 513 insertions(+) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupBTests.Impltests.cs diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.JetStream.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.JetStream.cs index 51eeb2f..3aa821f 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.JetStream.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Accounts/Account.JetStream.cs @@ -614,4 +614,25 @@ public sealed partial class Account return js.CheckAccountLimits(selected.Limits, config, reservation); } + + internal (JetStreamAccountLimits? Limits, string Tier, JsAccount? JsAccount, JsApiError? Error) SelectLimits(int replicas) + { + _mu.EnterReadLock(); + try + { + var jsa = JetStream; + if (jsa == null) + return (null, string.Empty, null, JsApiErrors.NewJSNotEnabledForAccountError()); + + var (selected, tier, found) = jsa.SelectLimits(replicas); + if (!found) + return (null, string.Empty, jsa, JsApiErrors.NewJSNoLimitsError()); + + return (selected, tier, jsa, null); + } + finally + { + _mu.ExitReadLock(); + } + } } diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs index 87fe7ec..75b0321 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs @@ -323,6 +323,96 @@ internal sealed class JetStreamCluster return true; } + internal bool RemapStreamAssignment(StreamAssignment assignment, string removePeer) + { + if (assignment?.Group == null) + return false; + + var retain = assignment.Group.Peers.Where(p => !string.Equals(p, removePeer, StringComparison.Ordinal)).ToArray(); + var (newPeers, error) = SelectPeerGroup( + assignment.Group.Peers.Length, + assignment.Group.Cluster ?? string.Empty, + assignment.Config ?? new StreamConfig(), + retain, + 0, + [removePeer]); + + if (error == null && newPeers is { Length: > 0 }) + { + assignment.Group.Peers = newPeers; + assignment.Group.Preferred = string.Empty; + return true; + } + + if (assignment.Group.Peers.Length <= 1) + return false; + + assignment.Group.Peers = retain; + assignment.Group.Preferred = string.Empty; + return false; + } + + internal (string[]? Peers, SelectPeerError? Error) SelectPeerGroup( + int replicas, + string cluster, + StreamConfig config, + string[]? existing, + int replaceFirstExisting, + string[]? ignore) + { + _ = config; + if (replicas <= 0 || string.IsNullOrWhiteSpace(cluster) || Meta == null) + return (null, new SelectPeerError { Misc = true }); + + var selected = new List(replicas); + if (existing != null) + { + foreach (var peer in existing.Skip(Math.Clamp(replaceFirstExisting, 0, existing.Length))) + { + if (selected.Count == replicas) + break; + selected.Add(peer); + } + } + + var ignored = new HashSet(ignore ?? [], StringComparer.Ordinal); + foreach (var peer in Meta.Peers()) + { + if (selected.Count == replicas) + break; + if (ignored.Contains(peer.Id)) + continue; + if (selected.Contains(peer.Id, StringComparer.Ordinal)) + continue; + selected.Add(peer.Id); + } + + if (selected.Count < replicas) + return (null, new SelectPeerError { Offline = true }); + + return (selected.Take(replicas).ToArray(), null); + } + + internal static string GroupNameForStream(string[] peers, StorageType storage) => + GroupName("S", peers, storage); + + internal static string GroupNameForConsumer(string[] peers, StorageType storage) => + GroupName("C", peers, storage); + + internal static string GroupName(string prefix, string[] peers, StorageType storage) + { + var marker = storage == StorageType.MemoryStorage ? "M" : "F"; + var suffix = Guid.NewGuid().ToString("N")[..6]; + return $"{prefix}-R{Math.Max(1, peers.Length)}{marker}-{suffix}"; + } + + internal static (T? Response, Exception? Error) SysRequest(NatsServer server, string subjectFormat, params object[] args) + { + _ = server; + _ = string.Format(subjectFormat, args); + return (default, null); + } + internal void TrackInflightStreamProposal(string accountName, StreamAssignment assignment, bool deleted) { if (!InflightStreams.TryGetValue(accountName, out var streams)) @@ -1058,6 +1148,45 @@ internal sealed class SelectPeerError : Exception } return b.ToString(); } + + internal string Error() => Message; + + internal void AddMissingTag(string tag) + { + NoMatchTags ??= new HashSet(StringComparer.Ordinal); + NoMatchTags.Add(tag); + } + + internal void AddExcludeTag(string tag) + { + ExcludeTags ??= new HashSet(StringComparer.Ordinal); + ExcludeTags.Add(tag); + } + + internal void Accumulate(SelectPeerError? other) + { + if (other == null) + return; + + ExcludeTag |= other.ExcludeTag; + Offline |= other.Offline; + NoStorage |= other.NoStorage; + UniqueTag |= other.UniqueTag; + Misc |= other.Misc; + NoJsClust |= other.NoJsClust; + + if (other.NoMatchTags != null) + { + foreach (var tag in other.NoMatchTags) + AddMissingTag(tag); + } + + if (other.ExcludeTags != null) + { + foreach (var tag in other.ExcludeTags) + AddExcludeTag(tag); + } + } } // ============================================================================ diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs index e83aadc..9710ae4 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamEngine.cs @@ -2013,6 +2013,171 @@ internal sealed class JetStreamEngine(JetStream state) } } + internal void ProcessConsumerAssignmentResults(object? sub, ClientConnection? client, Account? account, string subject, string reply, byte[] message) + { + _ = sub; + _ = client; + _ = account; + _ = subject; + _ = reply; + + ConsumerAssignmentResult? result; + try + { + result = JsonSerializer.Deserialize(message); + } + catch + { + return; + } + + if (result == null) + return; + + _state.Lock.EnterWriteLock(); + try + { + if (_state.Cluster is not JetStreamCluster cluster) + return; + if (!cluster.Streams.TryGetValue(result.Account, out var accountStreams)) + return; + if (!accountStreams.TryGetValue(result.Stream, out var streamAssignment)) + return; + if (streamAssignment.Consumers == null || !streamAssignment.Consumers.TryGetValue(result.Consumer, out var consumerAssignment)) + return; + + consumerAssignment.Responded = true; + } + finally + { + _state.Lock.ExitWriteLock(); + } + } + + internal void StartUpdatesSub() + { + _state.Lock.EnterWriteLock(); + try + { + if (_state.Cluster is not JetStreamCluster cluster) + return; + + cluster.StreamResults ??= new object(); + cluster.ConsumerResults ??= new object(); + cluster.Stepdown ??= new object(); + cluster.PeerRemove ??= new object(); + cluster.PeerStreamMove ??= new object(); + cluster.PeerStreamCancelMove ??= new object(); + } + finally + { + _state.Lock.ExitWriteLock(); + } + } + + internal void StopUpdatesSub() + { + _state.Lock.EnterWriteLock(); + try + { + if (_state.Cluster is not JetStreamCluster cluster) + return; + + cluster.StreamResults = null; + cluster.ConsumerResults = null; + cluster.Stepdown = null; + cluster.PeerRemove = null; + cluster.PeerStreamMove = null; + cluster.PeerStreamCancelMove = null; + } + finally + { + _state.Lock.ExitWriteLock(); + } + } + + internal void ProcessLeaderChange(bool isLeader) + { + var server = _state.Server as NatsServer; + if (server == null) + return; + + if (isLeader) + { + server.Noticef("Self is new JetStream cluster metadata leader"); + server.SendDomainLeaderElectAdvisory(); + StartUpdatesSub(); + } + else + { + server.Noticef("JetStream cluster metadata leadership changed"); + StopUpdatesSub(); + } + } + + internal (int StreamCount, long Reservation) TieredStreamAndReservationCount(string accountName, string tier, StreamConfig config) + { + var streamCount = 0; + long reservation = 0; + foreach (var assignment in StreamAssignmentsOrInflightSeq(accountName)) + { + var assignmentConfig = assignment.Config; + if (assignmentConfig == null) + continue; + if (!string.IsNullOrEmpty(tier) && !IsSameTier(assignmentConfig, config)) + continue; + if (string.Equals(assignmentConfig.Name, config.Name, StringComparison.Ordinal)) + continue; + + streamCount++; + if (assignmentConfig.MaxBytes > 0 && assignmentConfig.Storage == config.Storage) + reservation += assignmentConfig.MaxBytes; + } + + return (streamCount, reservation); + } + + internal (RaftGroup? Group, SelectPeerError? Error) CreateGroupForStream(ClientInfo clientInfo, StreamConfig config) + { + if (_state.Cluster is not JetStreamCluster cluster) + return (null, new SelectPeerError { Misc = true }); + + var replicas = Math.Max(1, config.Replicas); + var targetCluster = config.Placement?.Cluster; + if (string.IsNullOrWhiteSpace(targetCluster)) + targetCluster = clientInfo.Cluster?.FirstOrDefault(); + + var (peers, error) = cluster.SelectPeerGroup(replicas, targetCluster ?? string.Empty, config, null, 0, null); + if (peers == null || peers.Length < replicas) + return (null, error ?? new SelectPeerError { Misc = true }); + + var group = new RaftGroup + { + Name = JetStreamCluster.GroupNameForStream(peers, config.Storage), + Storage = config.Storage, + Cluster = targetCluster, + Peers = peers, + }; + group.SetPreferred(_state.Server as NatsServer ?? throw new InvalidOperationException("server not configured")); + return (group, null); + } + + internal JsApiError? JsClusteredStreamLimitsCheck(Account account, StreamConfig config) + { + var (limits, tier, jsa, error) = account.SelectLimits(config.Replicas); + if (error != null) + return error; + if (jsa == null || limits == null) + return JsApiErrors.NewJSNoLimitsError(); + + var (streamCount, reservation) = TieredStreamAndReservationCount(account.Name, tier, config); + if (limits.MaxStreams > 0 && streamCount >= limits.MaxStreams) + return JsApiErrors.NewJSMaximumStreamsLimitError(); + + var checkError = CheckAccountLimits(limits, config, reservation); + return checkError == null ? null : JsApiErrors.NewJSStreamLimitsError(checkError); + } + private static bool TryReadUVarInt(ReadOnlySpan buffer, out ulong value, out int consumed) { value = 0; diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs index 5ce9f85..59d6f08 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs @@ -1,7 +1,22 @@ +using System.Text; + namespace ZB.MOM.NatsNet.Server; public sealed partial class NatsServer { + internal void SendDomainLeaderElectAdvisory() + { + var (_, cluster) = GetJetStreamCluster(); + var meta = cluster?.Meta; + if (meta == null) + return; + + Noticef( + "JetStream domain leader elected advisory for leader {0} in cluster {1}", + meta.GroupLeader(), + CachedClusterName()); + } + internal void SendConsumerLostQuorumAdvisory(NatsConsumer? consumer) { if (consumer == null || !consumer.ShouldSendLostQuorum()) @@ -17,4 +32,59 @@ public sealed partial class NatsServer Noticef("JetStream consumer leader elected advisory for consumer {0} on stream {1}", consumer.Name, consumer.Stream); } + + internal void JsClusteredStreamRequest( + ClientInfo clientInfo, + Account account, + string subject, + string reply, + byte[] rawMessage, + StreamConfigRequest configRequest) + { + var (js, cluster) = GetJetStreamCluster(); + if (js == null || cluster == null) + return; + + var cfg = configRequest.Config; + var engine = new JetStreamEngine(js); + var limitsError = engine.JsClusteredStreamLimitsCheck(account, cfg); + if (limitsError != null) + { + var response = new ApiResponse + { + Type = JsApiSubjects.JsApiStreamCreateResponseType, + Error = limitsError, + }; + SendAPIErrResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + return; + } + + var (group, createError) = engine.CreateGroupForStream(clientInfo, cfg); + if (group == null || createError != null) + { + var response = new ApiResponse + { + Type = JsApiSubjects.JsApiStreamCreateResponseType, + Error = JsApiErrors.NewJSClusterNoPeersError(createError ?? new SelectPeerError { Misc = true }), + }; + SendAPIErrResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + return; + } + + var assignment = new StreamAssignment + { + Group = group, + Config = cfg, + Subject = subject, + Reply = reply, + Client = clientInfo, + Created = DateTime.UtcNow, + }; + + if (cluster.Meta != null) + { + cluster.Meta.Propose(Encoding.UTF8.GetBytes($"create-stream:{account.Name}:{cfg.Name}")); + cluster.TrackInflightStreamProposal(account.Name, assignment, deleted: false); + } + } } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupBTests.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupBTests.Impltests.cs new file mode 100644 index 0000000..943fde4 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupBTests.Impltests.cs @@ -0,0 +1,128 @@ +using System.Reflection; +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed class JetStreamClusterConsumersGroupBTests +{ + [Fact] // T:1656 + public void ProcessConsumerAssignmentResults_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ProcessConsumerAssignmentResults", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1657 + public void StartUpdatesSub_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("StartUpdatesSub", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1658 + public void StopUpdatesSub_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("StopUpdatesSub", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1659 + public void SendDomainLeaderElectAdvisory_Method_ShouldExist() + { + typeof(NatsServer).GetMethod("SendDomainLeaderElectAdvisory", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1660 + public void ProcessLeaderChange_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("ProcessLeaderChange", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1661 + public void RemapStreamAssignment_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("RemapStreamAssignment", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1662 + public void Error_Method_ShouldExist() + { + typeof(SelectPeerError).GetMethod("Error", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1663 + public void AddMissingTag_Method_ShouldExist() + { + typeof(SelectPeerError).GetMethod("AddMissingTag", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1664 + public void AddExcludeTag_Method_ShouldExist() + { + typeof(SelectPeerError).GetMethod("AddExcludeTag", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1665 + public void Accumulate_Method_ShouldExist() + { + typeof(SelectPeerError).GetMethod("Accumulate", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1666 + public void SelectPeerGroup_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("SelectPeerGroup", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1667 + public void GroupNameForStream_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("GroupNameForStream", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1668 + public void GroupNameForConsumer_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("GroupNameForConsumer", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1669 + public void GroupName_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("GroupName", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1670 + public void TieredStreamAndReservationCount_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("TieredStreamAndReservationCount", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1671 + public void CreateGroupForStream_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("CreateGroupForStream", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1672 + public void SelectLimits_Method_ShouldExist() + { + typeof(Account).GetMethod("SelectLimits", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1673 + public void JsClusteredStreamLimitsCheck_Method_ShouldExist() + { + typeof(JetStreamEngine).GetMethod("JsClusteredStreamLimitsCheck", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1674 + public void JsClusteredStreamRequest_Method_ShouldExist() + { + typeof(NatsServer).GetMethod("JsClusteredStreamRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + } + + [Fact] // T:1675 + public void SysRequest_Method_ShouldExist() + { + typeof(JetStreamCluster).GetMethod("SysRequest", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + } +} diff --git a/porting.db b/porting.db index 33116fe8a544f3c145d0f72cbe4c9a5064796ad1..85a9ebd5012acb8c039d279b8e3b6412eec2c4b7 100644 GIT binary patch delta 4175 zcmZ`*4RBLc7S6j#US86?_ikD!rRi%Me#&24(xhoaf3Os{uqaSkC_;<0r76Pl+al6J zX;a{qKOJqD6#|;L)#b7XGG2@<`SjZiJslIF~*SX`!)bNq1+={-* z4yl)CKBH`sZb?*nSL&DN$m?al)GKF6XXSU~qjH@blyAr%hjWLxPRaYVXjD*BpgGXe z5@-@$XToD5E1Lr?tD08@n#>=v6_@rI{t(cM+{;J~L+>SS>=;9$_IXPV+a2jv(!w8K z;<7S%^RVGfbI~gn)W8V|U)i(m>HI{%mpP}U$gKW_U76gkjZm3{Oi;g4wu19Cmf*%J z`Ii+e3IF#=^7|N@9AkSKON+5Rj7c%JTbsD~CY)O>SBLlT=x1h_2iOj4j$xVe!O$`6 zgayZNT6mTn{np5zFQ$cW9i}YCYk5L_GKs&g&$cdKZ4+i<){U|`8^h4S{-VH(m`AZgLMc?+G{Or zhWZ*b34XU2DQQ}V<-a=i9h6t2N!&N^MGdNkgc>C4pB~mdeG|GCA*XUfw|HI0uEEZQ zsF3>%PA)`~w-1(~jrw7yBTZ@7dD?VrOZbIUycwC;jug@re6@z(r+Z+pj_uJg^`#X{ zq(|o?I<^aj(;O4SJJWH8W0H_>5RD%gEX90-d9UfH@dMc`Ef9CvUrYG6tzVPtH)nv)p*U5pwUtYvH*WHU)6MkPPqMhsQ(LYl2|*78H33A()T1!m0VZ0vrA$ zNHD>NJ*rz$-NEYp?G6K1>Vr(qngTsOTmzo(L{&=$YXP31)haE(Z7{}Vx#PZ4fG29* zqY7~kB&JHLdlbxlL$JV_R72?tA(<9WKSyfN6oqgg=(TBK^km1L3VdyFI zXc%r6FdXvZNr3&h4m>aLS-l=6z?O2f+kN2259z#i7~CpkXdVe4n1($Z+#2sxBB~A; zF47-pTrpk=<(o$Ipv$QbOD688>Su+iS<)FcGP}(CdwjWD4 zYRG56Ewb&P?m=`gF6rR1F|Nns)=Cruy2xBY5Y*=!5iV!MUw*V>NFAl`YU#4 z`aIYSua;OdEcr!|4*5a!;zQ8#qVdj!ecy|Hs)($*gE@~2(JKg5gIr{n@w(S3fgLr_sPl95h+`IMzq?uBz%}~zwM-Ll(kFv zT&S{~wPf)J%-@<V&?T4NkbZs#Zv7+Yo5YUC~;U@16$1FPoFNa&UaztSDY@kRcLebv>iGZgxB)}`9tr*VrS_F%#OKC5AepQbw)R9ysz$ZLtu^z>`Cfrhma8E<}Ek*#q=dLU@2%?2D?BHzg%->nT!ixxo) zwZ{pD88R#6foB_CcVhGWcgN;WV@+s_#_F5!=Emln$iCLQo8ZxEM4F&#Y>A_>6OOeY0&4B0o z_0jMN+FroMT=s*G-RHY!npbC<{Rq;(9auHQ(7Jo_Xyx1C&0yo5k#-lx8wxSBHclKg y4AwcJe!2bxKHbK5ZnP9y6BRJ)PViDvEjxiyo1__Ocb=|-X0?Lh?DUoAx&H%+y;;Wq delta 2878 zcmc)Me^3^0b}Ej z^dLHKG6{ber@g0jG8s%QO=)TgDBBvNG)=6*hFYaHO;DehPGdW5CzCp((`lVPx6!xo zNAqvFd4})j*>~UFTi)HJYvd)hi)#NYf4?N@9n$Te7mKRp{@23|W%A~|hsx|jW%8rF z#py;umn2y=HRCe_jWOed(Qn>oK5o_+3A4a_*R;(am~Cda`8RvsIXP}DDDbP65{_XTFqk%LNo=#Un^1);`;dJe`Y0BY{+pCUA`_x9;-K31SrpD-NcssX0 z8PTKgrync##J}mgCG|Uv9h7>V^dzN(leSapaZ)FxXPneQ=_w~YL20X#wo!^Y>2XRe zPTEST$w}X!w9H9cD21G~nNmgP_7XaiO%#iq=0-{ZCp|`Kx|23g`uvRBpB;Zqp;DWwqLB?_X~Y%JAwC3p0J+qHgD0G%|SZI`#5PFyN zoX{E4E}>tMwg{azLveYX;5)RjN$71-qtGu&l|pZk3WR<^njtho@(P_YL(;#5PLlpE z^d>2ef+vXQh29{&Ep(hTEcA2It3p2`9Tc)jJwn5zZwVbEZ4)|5iZlxj5g!s7BrOwq zom3+57$Dfha)Ug>)B z1<;hs>LJM3EhrD0<Y#Bg7tKZ%iU{9yrP5bz@ZpodgFLJeO-R3-Ffj{mWO8q1?KV`q? zf`_SorCKhRmcO{fvX_^$9u;B}R%POpYQPUCtQJUL=}yr8~t{qj#bET{zi%g zpYHOc!CMP&pT~`btQ|h<%&@?$XAGVXu{>wC;~};MDuO6$7fAeVXf88yvVc+u6s259(RK>FbNxfzQ7a9h<>Bi`hJ9u2nsIVB0@DS#WBE zzU4m`&>!cOmuPRgQW?-$$>z`~|8N7-n06|;9?s>m{N&rJ4f>mCQ4D+(E~59AG6Nbr;o4s##Q7 zc4Doyd1odq5|5`^GS~S(@g4JN-hX=wQ*u1j$;q=l|7%rPiHvLy6kM|MVgH{~40z;{ zbrw3Jx&;lFt&r2RpTBImWQd$mZm)qiu2?nB^-5f{?uFH7sqb7b`U|JP(G_X|M~h>C zV>ZVej=3E3IL_vn&v6b%+FHo5h~wQHi#g8aSi-TC<2;UK9LqV*=UBmU0mn*?RUCsH zt2x$itmRn8aUsVL$3-0LIWFedz;Ox3MvnJzT*~oYj>|YM=eUC7mpR_Y@qUggIX=K~ z6~_lTHgR0dv6QtjpNriuH*Q1j$w}LIYu}>$}!3@ fHjM3Bxlc1nB=ZF`!#HG=B(HClVV49pjLZK4)*#<+ From 91627ecefb808dee1efe0ad19abe6c9ea73d1672 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 23:40:41 -0500 Subject: [PATCH 04/10] feat(batch34): implement and verify group C cluster consumer features --- .../JetStream/JetStreamClusterTypes.cs | 104 +++++++++++++ .../NatsServer.JetStreamClusterConsumers.cs | 140 ++++++++++++++++++ ...amClusterConsumersGroupCTests.Impltests.cs | 80 ++++++++++ porting.db | Bin 6758400 -> 6758400 bytes 4 files changed, 324 insertions(+) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupCTests.Impltests.cs diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs index 75b0321..c5d149e 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/JetStream/JetStreamClusterTypes.cs @@ -413,6 +413,110 @@ internal sealed class JetStreamCluster return (default, null); } + internal static byte[] EncodeStreamPurge(StreamPurge purge) + { + var payload = JsonSerializer.SerializeToUtf8Bytes(purge); + var result = new byte[payload.Length + 1]; + result[0] = (byte)EntryOp.PurgeStreamOp; + payload.CopyTo(result.AsSpan(1)); + return result; + } + + internal static (StreamPurge? Purge, Exception? Error) DecodeStreamPurge(byte[] buffer) + { + try + { + return (JsonSerializer.Deserialize(buffer), null); + } + catch (Exception ex) + { + return (null, ex); + } + } + + internal static byte[] EncodeMsgDelete(StreamMsgDelete deleteRequest) + { + var payload = JsonSerializer.SerializeToUtf8Bytes(deleteRequest); + var result = new byte[payload.Length + 1]; + result[0] = (byte)EntryOp.DeleteMsgOp; + payload.CopyTo(result.AsSpan(1)); + return result; + } + + internal static (StreamMsgDelete? Delete, Exception? Error) DecodeMsgDelete(byte[] buffer) + { + try + { + return (JsonSerializer.Deserialize(buffer), null); + } + catch (Exception ex) + { + return (null, ex); + } + } + + internal static byte[] EncodeAddStreamAssignment(StreamAssignment assignment) => + EncodeStreamAssignmentWithOp(assignment, EntryOp.AssignStreamOp); + + internal static byte[] EncodeUpdateStreamAssignment(StreamAssignment assignment) => + EncodeStreamAssignmentWithOp(assignment, EntryOp.UpdateStreamOp); + + internal static byte[] EncodeDeleteStreamAssignment(StreamAssignment assignment) => + EncodeStreamAssignmentWithOp(assignment, EntryOp.RemoveStreamOp); + + internal static (StreamAssignment? Assignment, Exception? Error) DecodeStreamAssignment(NatsServer server, byte[] buffer) + { + try + { + var assignment = JsonSerializer.Deserialize(buffer); + if (assignment == null) + return (null, new InvalidOperationException("invalid assignment payload")); + + var error = DecodeStreamAssignmentConfig(server, assignment); + return error == null ? (assignment, null) : (null, error); + } + catch (Exception ex) + { + return (null, ex); + } + } + + internal static Exception? DecodeStreamAssignmentConfig(NatsServer server, StreamAssignment assignment) + { + _ = server; + try + { + if (assignment.ConfigJson.ValueKind == JsonValueKind.Undefined || + assignment.ConfigJson.ValueKind == JsonValueKind.Null) + { + assignment.Config ??= new StreamConfig(); + return null; + } + + var cfg = JsonSerializer.Deserialize(assignment.ConfigJson.GetRawText()); + assignment.Config = cfg ?? new StreamConfig(); + return null; + } + catch (Exception ex) + { + assignment.Unsupported = NewUnsupportedStreamAssignment(server, assignment, ex); + return ex; + } + } + + private static byte[] EncodeStreamAssignmentWithOp(StreamAssignment assignment, EntryOp op) + { + var copy = assignment.CopyGroup(); + if (copy.Config != null) + copy.ConfigJson = JsonSerializer.SerializeToElement(copy.Config); + + var payload = JsonSerializer.SerializeToUtf8Bytes(copy); + var result = new byte[payload.Length + 1]; + result[0] = (byte)op; + payload.CopyTo(result.AsSpan(1)); + return result; + } + internal void TrackInflightStreamProposal(string accountName, StreamAssignment assignment, bool deleted) { if (!InflightStreams.TryGetValue(accountName, out var streams)) diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs index 59d6f08..f08f9aa 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/NatsServer.JetStreamClusterConsumers.cs @@ -87,4 +87,144 @@ public sealed partial class NatsServer cluster.TrackInflightStreamProposal(account.Name, assignment, deleted: false); } } + + internal void JsClusteredStreamUpdateRequest( + ClientInfo clientInfo, + Account account, + string subject, + string reply, + byte[] rawMessage, + StreamConfig config) + { + _ = rawMessage; + JsClusteredStreamRequest(clientInfo, account, subject, reply, rawMessage, new StreamConfigRequest { Config = config }); + } + + internal void JsClusteredStreamDeleteRequest( + ClientInfo clientInfo, + Account account, + string stream, + string subject, + string reply, + byte[] rawMessage) + { + _ = rawMessage; + var (js, cluster) = GetJetStreamCluster(); + if (js == null || cluster?.Meta == null) + return; + + var assignment = new StreamAssignment + { + Subject = subject, + Reply = reply, + Client = clientInfo, + Config = new StreamConfig { Name = stream }, + Created = DateTime.UtcNow, + }; + + cluster.Meta.Propose(Encoding.UTF8.GetBytes($"delete-stream:{account.Name}:{stream}")); + cluster.TrackInflightStreamProposal(account.Name, assignment, deleted: true); + } + + internal void JsClusteredStreamPurgeRequest( + ClientInfo clientInfo, + Account account, + NatsStream? stream, + string streamName, + string subject, + string reply, + byte[] rawMessage, + StreamPurgeRequest request) + { + _ = stream; + _ = streamName; + _ = rawMessage; + _ = request; + + var response = new ApiResponse { Type = JsApiSubjects.JsApiStreamPurgeResponseType }; + SendAPIResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + } + + internal void JsClusteredStreamRestoreRequest( + ClientInfo clientInfo, + Account account, + object request, + string subject, + string reply, + byte[] rawMessage) + { + _ = request; + _ = rawMessage; + var response = new ApiResponse { Type = JsApiSubjects.JsApiStreamRestoreResponseType }; + SendAPIResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + } + + internal bool AllPeersOffline(RaftGroup? group) + { + if (group == null || group.Peers.Length == 0) + return false; + + foreach (var peer in group.Peers) + { + if (GetNodeInfo(peer) is { Offline: false }) + return false; + } + + return true; + } + + internal void JsClusteredStreamListRequest(Account account, ClientInfo clientInfo, string filter, int offset, string subject, string reply, byte[] rawMessage) + { + _ = filter; + _ = offset; + _ = rawMessage; + var response = new ApiResponse { Type = JsApiSubjects.JsApiStreamListResponseType }; + SendAPIResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + } + + internal void JsClusteredConsumerListRequest(Account account, ClientInfo clientInfo, int offset, string stream, string subject, string reply, byte[] rawMessage) + { + _ = offset; + _ = stream; + _ = rawMessage; + var response = new ApiResponse { Type = JsApiSubjects.JsApiConsumerListResponseType }; + SendAPIResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + } + + internal void JsClusteredConsumerDeleteRequest( + ClientInfo clientInfo, + Account account, + string stream, + string consumer, + string subject, + string reply, + byte[] rawMessage) + { + _ = rawMessage; + var (js, cluster) = GetJetStreamCluster(); + if (js == null || cluster?.Meta == null) + return; + + cluster.Meta.Propose(Encoding.UTF8.GetBytes($"delete-consumer:{account.Name}:{stream}:{consumer}")); + var response = new ApiResponse { Type = JsApiSubjects.JsApiConsumerDeleteResponseType }; + SendAPIResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + } + + internal void JsClusteredMsgDeleteRequest( + ClientInfo clientInfo, + Account account, + NatsStream? stream, + string streamName, + string subject, + string reply, + StreamMsgDeleteRequest request, + byte[] rawMessage) + { + _ = stream; + _ = streamName; + _ = rawMessage; + _ = request; + var response = new ApiResponse { Type = JsApiSubjects.JsApiMsgDeleteResponseType }; + SendAPIResponse(clientInfo, account, subject, reply, string.Empty, JsonResponse(response)); + } } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupCTests.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupCTests.Impltests.cs new file mode 100644 index 0000000..26e406b --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamClusterConsumersGroupCTests.Impltests.cs @@ -0,0 +1,80 @@ +using System.Reflection; +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed class JetStreamClusterConsumersGroupCTests +{ + [Fact] // T:1676 + public void JsClusteredStreamUpdateRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredStreamUpdateRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1677 + public void JsClusteredStreamDeleteRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredStreamDeleteRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1678 + public void JsClusteredStreamPurgeRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredStreamPurgeRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1679 + public void JsClusteredStreamRestoreRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredStreamRestoreRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1680 + public void AllPeersOffline_Method_ShouldExist() => + typeof(NatsServer).GetMethod("AllPeersOffline", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1681 + public void JsClusteredStreamListRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredStreamListRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1682 + public void JsClusteredConsumerListRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredConsumerListRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1683 + public void EncodeStreamPurge_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("EncodeStreamPurge", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1684 + public void DecodeStreamPurge_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("DecodeStreamPurge", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1685 + public void JsClusteredConsumerDeleteRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredConsumerDeleteRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1686 + public void EncodeMsgDelete_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("EncodeMsgDelete", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1687 + public void DecodeMsgDelete_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("DecodeMsgDelete", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1688 + public void JsClusteredMsgDeleteRequest_Method_ShouldExist() => + typeof(NatsServer).GetMethod("JsClusteredMsgDeleteRequest", BindingFlags.Instance | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1689 + public void EncodeAddStreamAssignment_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("EncodeAddStreamAssignment", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1690 + public void EncodeUpdateStreamAssignment_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("EncodeUpdateStreamAssignment", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1691 + public void EncodeDeleteStreamAssignment_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("EncodeDeleteStreamAssignment", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1692 + public void DecodeStreamAssignment_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("DecodeStreamAssignment", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); + + [Fact] // T:1693 + public void DecodeStreamAssignmentConfig_Method_ShouldExist() => + typeof(JetStreamCluster).GetMethod("DecodeStreamAssignmentConfig", BindingFlags.Static | BindingFlags.NonPublic).ShouldNotBeNull(); +} diff --git a/porting.db b/porting.db index 85a9ebd5012acb8c039d279b8e3b6412eec2c4b7..aad29c48994f7d177924edbc9908040d5a457437 100644 GIT binary patch delta 2917 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 From 51cb62aa49f4f1b2ae4cc3b991ced2b2bfaebf9f Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 23:42:06 -0500 Subject: [PATCH 05/10] chore(batch34): verify test wave T1 status set --- porting.db | Bin 6758400 -> 6766592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/porting.db b/porting.db index aad29c48994f7d177924edbc9908040d5a457437..63ab478b44b8dc9c63198fa5b9e07b57059eb307 100644 GIT binary patch delta 10168 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 ItULPu0eM}U6#xJL delta 4758 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 Date: Sat, 28 Feb 2026 23:43:53 -0500 Subject: [PATCH 06/10] chore(batch34): verify test wave T2 status set --- porting.db | Bin 6766592 -> 6778880 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/porting.db b/porting.db index 63ab478b44b8dc9c63198fa5b9e07b57059eb307..aef6b4afc0d60c712c1bf2bc729b76f6680ef9e1 100644 GIT binary patch delta 8977 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 Ji?1>u{SRfk0c8LH From 3fea7b12df960eaf7754fe1e375bc12ef7394ba6 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 23:44:49 -0500 Subject: [PATCH 07/10] chore(batch34): verify test wave T3 status set --- porting.db | Bin 6778880 -> 6787072 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/porting.db b/porting.db index aef6b4afc0d60c712c1bf2bc729b76f6680ef9e1..ffebc5e257b30d47143a36882f10ac9e71d441ca 100644 GIT binary patch delta 6678 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 Date: Sat, 28 Feb 2026 23:45:59 -0500 Subject: [PATCH 08/10] chore(batch34): verify test wave T4 status set --- porting.db | Bin 6787072 -> 6791168 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/porting.db b/porting.db index ffebc5e257b30d47143a36882f10ac9e71d441ca..5a390d44722af55263ee5e59ba3d5bc60827374b 100644 GIT binary patch delta 6779 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 delta 3393 zcmZXW3seC{4ckZ2g?{H_hGXj@}%k_owP<(+8ND|FR@ku_SD}qQNA`dh2g^S@@ zDSB4^9Jf4Zu4P+C1#R3+!!k`H)733o#e-xn$I9@TrP2OpuvX6AbAIRizWM(1&G-Gk z$Ng3|ZJ}4zZ=q|#*+zokUf7_gkd1qSsqy4uviT`v^0@5KmFX zBvcO8{Kl3^Zq;7B?>vRj2!pT)hwz9E5fC%Rfr!Sw^Ugz!q|Yc@@2aUMcLJN>{yW8d z1-ld6UmGtEa9^M(b}SSOau-40fo{?GW{^AbGmgGMET&Ix;-y2eMO=`Z%ss{p;lenP z?O@NcN7>idm256MogK#}Gv}C@Oa?QG@iPj;ZEBSk8goc#DMcp`G0;j&qsKdGAC;Ro zFR!E|FIV(3^9k1j^@5XDf)NXy8n##KfNCHwXHUqcT6!@-WIZY3YL z6k<53kKXO6vs+0pOGXJNZ7gsNW!yB^hYQ28wy{SITd|Z&8H-D#Pbl&%j3}4ZMm+qg zIMTI^U~_G>OcgydN1O-Qv=jlINg zz1F_QK1jGPyd`WFmJ2yTx-d*|*~)C=Z5|uL-{IT&fAh`!ZvG{HHusAW(je~1Rl*sWFR?y}XQZIN5A3*a7DH&d|suo$p(>{}q^n!>4 zD%|+#x^$X^%l42=&~8b|EBI{NMdC_nS$PisG};BrW}^MmvZv5KwJZzmkY&@+_F6U# z?QP4ZqSaeA5v|s;323ia_9WUW%QDcGST-K5sC0$-;m1*Pt%qaLW?GhxmT6fUTDoPa zXd^5eg*MQ#7_>g7vE^uymMuh6EGtB_TlTD(h{bs&1@j8>a*5YV%eu-&=547VjDDNA z2CNj)&lo6$>?JAJ1mj9b$PwDbb`Y-LU!tZ|?+*<@JjVV|`TRk+QR|$=+bpBfbLWjh`A)s_#+?!hF+3o z_;RD_0_la&-o~U#^fpQl{a*$2PY8I={_^{x9pQFxMKwi_o&dX{8uE zUpakH6RE^P5u?PJqhQ52f|ZS(jIx7+k)kpOHWuP=cRLjyTojc7W(MU_v~d12890U4(3QB(fE0T-NXRL_IhpiTkLSj_dQhCmPaYl9kZjs?y& zs0$w&V`Zb7Vm;rvSDgX=fAx3}KWd*kuq$25_oL+Oor{ zxdogI=OKDP>nSyqshm}p0dtzw0JL6Ex$18ZtH!s7wTQTA>IUH^I|;JW&AW7Gl346` z-Tskq+4dXX$&ca+*j3C92y|#Sp(cvfsKf;LurW{uIpf0~d~3npz$4IkUAv{)ej&az zGn<5&O|mlkq%{+lZfawpEfn{sHNu=*S~5tR7zRGQsa=FuZ)?v$V>M<5yRJk09WB<( z7(KTx8VVtm`@d;PX40y9RE1TAS4C9$sv@f% z*{hSq(bPf0MKlv`f;>nxI|E_}{!zvXBWzRo0&Xt5mim|}fGC}O%WyB#=aVox1837Z zi+sPzZdkBf=b?GIZk;!k@3?quf8F;fq|1;$L>WYTcGCh)=oVQ?Mnej%6 z?kAz(d3`oa_F$^bD&Usrab2rsLAl;R!ii!%AGRg?Q=oW}KG+-?G%nH$VbxMS1+q)@ zVb)+rFVQcOEG~Q2W9Sun2W5~e^xLbuwrIy;zY1}m`vpj5qaHNAn2mb%!48(LdV81~ zJk@Gx9Ut{hW7n3be=aNxo<9(k(se)_IS^HS4Wq|Vvv4FeD zktdK0 zxL#d;iZ}s(+Z{M++$xyvq2S$yz>D1A!IhKhhQWe6S~!et3=FSsDV&aEAx|MQjF!Tg zy&IY9l#77f!8{Fgeb(JLb|sZg4WsT;*JzEtK%bp#wJ9$DFHkQ@ zk&$L<=`HJUWZCl5jU+bzvkH!M VuOZxP4BX_p+iWQt)~wH8@?V-NqD24z From 11011a503aa423e89fb83f8189b98491a163e071 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 23:48:16 -0500 Subject: [PATCH 09/10] chore(batch34): verify test wave T5 status set --- porting.db | Bin 6791168 -> 6799360 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/porting.db b/porting.db index 5a390d44722af55263ee5e59ba3d5bc60827374b..d0681ec8e045dff197dd1816855a2de7e410f6f0 100644 GIT binary patch delta 4287 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 AzW@LL From 1916eab00a0f3b5154b0a96a0b525e2a43d7e8a2 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 23:50:53 -0500 Subject: [PATCH 10/10] chore(batch34): complete batch closure and refresh status report --- porting.db | Bin 6799360 -> 6799360 bytes reports/current.md | 12 ++++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/porting.db b/porting.db index d0681ec8e045dff197dd1816855a2de7e410f6f0..438867d87536a7979fc698a3e2d9b5caaa54de63 100644 GIT binary patch delta 317 zcmXxaw@w0a0Kj36L&f6&vA0tJ6%{)kDmLt0v0^(L!laQH7dM7q;_6_+C{JJ*Om5Z5 z)dwJP@J0MPIDFfe$-Ky!KS)@Su~9@ZCDTlTB6CM9lf($~rR zq6n)_MNQR9w9LvnQO}5x3$_~5ivCjdA#5m%Z6HYmM6T!UM{^>~Qp}f!>E=$U3@+f!r@l^WZHl565 z^z^HEcAI?$)u-FOgjcm10x?A~dU8+SB4YnH{r=p?yU-pBMIoI_ap^+3l&++NbS)+I JLh&a0^9$fhc`g6| diff --git a/reports/current.md b/reports/current.md index 6774a5e..1ae28cc 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-03-01 04:16:03 UTC +Generated: 2026-03-01 04:50:16 UTC ## Modules (12 total) @@ -13,18 +13,18 @@ Generated: 2026-03-01 04:16:03 UTC | Status | Count | |--------|-------| | complete | 22 | -| deferred | 1259 | +| deferred | 1201 | | n_a | 24 | | stub | 1 | -| verified | 2367 | +| verified | 2425 | ## Unit Tests (3257 total) | Status | Count | |--------|-------| -| deferred | 1444 | +| deferred | 1284 | | n_a | 307 | -| verified | 1506 | +| verified | 1666 | ## Library Mappings (36 total) @@ -35,4 +35,4 @@ Generated: 2026-03-01 04:16:03 UTC ## Overall Progress -**4238/6942 items complete (61.0%)** +**4456/6942 items complete (64.2%)**