task6: implement batch38 group E state snapshot and wait queue

This commit is contained in:
Joseph Doherty
2026-03-01 00:39:08 -05:00
parent 9fbcafb2a3
commit df3b44789b
7 changed files with 291 additions and 0 deletions

View File

@@ -87,6 +87,14 @@ internal sealed partial class NatsConsumer
SendAckReply(reply);
}
internal bool ProcessAckMsg(ulong streamSequence, ulong deliverySequence, ulong deliveryCount, string reply, bool doSample)
{
ProcessAckMessage(streamSequence, deliverySequence, deliveryCount, reply);
if (doSample && NeedAck())
SampleAck(reply);
return true;
}
internal bool ProcessNak(ulong streamSequence, ulong deliverySequence, ulong deliveryCount, byte[] message)
{
ArgumentNullException.ThrowIfNull(message);

View File

@@ -1,10 +1,12 @@
using System.Text.Json;
using System.Text;
namespace ZB.MOM.NatsNet.Server;
internal sealed partial class NatsConsumer
{
private static readonly TimeSpan DefaultGatewayInterestInterval = TimeSpan.FromSeconds(1);
private ConsumerInfo? _initialInfo;
internal bool UpdateDeliveryInterest(bool localInterest)
{
@@ -582,4 +584,158 @@ internal sealed partial class NatsConsumer
_mu.ExitReadLock();
}
}
internal void ApplyState(ConsumerState state)
{
ArgumentNullException.ThrowIfNull(state);
_mu.EnterWriteLock();
try
{
_state = new ConsumerState
{
Delivered = new SequencePair
{
Consumer = state.Delivered.Consumer,
Stream = state.Delivered.Stream,
},
AckFloor = new SequencePair
{
Consumer = state.AckFloor.Consumer,
Stream = state.AckFloor.Stream,
},
Pending = state.Pending is null ? null : new Dictionary<ulong, Pending>(state.Pending),
Redelivered = state.Redelivered is null ? null : new Dictionary<ulong, ulong>(state.Redelivered),
};
}
finally
{
_mu.ExitWriteLock();
}
}
internal void SetStoreState(ConsumerState state) => ApplyState(state);
internal ConsumerState WriteStoreState()
{
_mu.EnterWriteLock();
try
{
return WriteStoreStateUnlocked();
}
finally
{
_mu.ExitWriteLock();
}
}
internal ConsumerState WriteStoreStateUnlocked() => GetConsumerState();
internal ConsumerInfo InitialInfo()
{
_mu.EnterWriteLock();
try
{
_initialInfo ??= GetInfo();
return _initialInfo;
}
finally
{
_mu.ExitWriteLock();
}
}
internal void ClearInitialInfo()
{
_mu.EnterWriteLock();
try
{
_initialInfo = null;
}
finally
{
_mu.ExitWriteLock();
}
}
internal ConsumerInfo Info() => GetInfo();
internal ConsumerInfo InfoWithSnap(ConsumerState? snapshot = null)
{
if (snapshot is null)
return GetInfo();
var info = GetInfo();
info.Delivered = new SequenceInfo { Consumer = snapshot.Delivered.Consumer, Stream = snapshot.Delivered.Stream };
info.AckFloor = new SequenceInfo { Consumer = snapshot.AckFloor.Consumer, Stream = snapshot.AckFloor.Stream };
return info;
}
internal (ConsumerInfo Info, string ReplySubject) InfoWithSnapAndReply(string replySubject, ConsumerState? snapshot = null) =>
(InfoWithSnap(snapshot), replySubject);
internal void SignalNewMessages() => _updateChannel.Writer.TryWrite(true);
internal bool ShouldSample()
{
if (string.IsNullOrWhiteSpace(Config.SampleFrequency))
return false;
var token = Config.SampleFrequency!.Trim().TrimEnd('%');
if (!int.TryParse(token, out var percent))
return false;
return percent > 0;
}
internal bool SampleAck(string ackReply)
{
if (!ShouldSample())
return false;
if (string.IsNullOrWhiteSpace(ackReply))
return false;
AddAckReply((ulong)ackReply.Length, ackReply);
return true;
}
internal bool IsFiltered(string subject)
{
if (string.IsNullOrWhiteSpace(subject))
return false;
if (!string.IsNullOrWhiteSpace(Config.FilterSubject))
return Internal.DataStructures.SubscriptionIndex.SubjectIsSubsetMatch(subject, Config.FilterSubject!);
if (Config.FilterSubjects is not { Length: > 0 })
return false;
return Config.FilterSubjects.Any(filter => Internal.DataStructures.SubscriptionIndex.SubjectIsSubsetMatch(subject, filter));
}
internal bool NeedAck() => Config.AckPolicy != AckPolicy.AckNone;
internal static (JsApiConsumerGetNextRequest? Request, Exception? Error) NextReqFromMsg(ReadOnlySpan<byte> message)
{
if (message.Length == 0)
return (new JsApiConsumerGetNextRequest { Batch = 1 }, null);
try
{
var text = Encoding.UTF8.GetString(message);
if (int.TryParse(text, out var batch))
return (new JsApiConsumerGetNextRequest { Batch = Math.Max(1, batch) }, null);
var req = JsonSerializer.Deserialize<JsApiConsumerGetNextRequest>(text);
if (req is null)
return (null, new InvalidOperationException("invalid request"));
if (req.Batch <= 0)
req.Batch = 1;
return (req, null);
}
catch (Exception ex)
{
return (null, ex);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace ZB.MOM.NatsNet.Server;
internal sealed partial class NatsConsumer
{
internal static WaitQueue NewWaitQueue(int max = 0) => WaitQueue.NewWaitQueue(max);
}

View File

@@ -456,6 +456,42 @@ public sealed class WaitingRequest
/// <summary>Optional pull request priority group metadata.</summary>
public PriorityGroup? PriorityGroup { get; set; }
public bool RecycleIfDone()
{
if (N > 0 || MaxBytes > 0 && B < MaxBytes)
return false;
Recycle();
return true;
}
public void Recycle()
{
Subject = string.Empty;
Reply = null;
N = 0;
D = 0;
NoWait = 0;
Expires = null;
MaxBytes = 0;
B = 0;
PriorityGroup = null;
}
}
public sealed class WaitingDelivery
{
public string Reply { get; set; } = string.Empty;
public ulong Sequence { get; set; }
public DateTime Created { get; set; } = DateTime.UtcNow;
public void Recycle()
{
Reply = string.Empty;
Sequence = 0;
Created = DateTime.UnixEpoch;
}
}
/// <summary>
@@ -681,6 +717,8 @@ public sealed class WaitQueue
}
private static int PriorityOf(WaitingRequest req) => req.PriorityGroup?.Priority ?? int.MaxValue;
public static WaitQueue NewWaitQueue(int max = 0) => new(max);
}
/// <summary>