batch37 task3 implement group B direct-get and pipeline

This commit is contained in:
Joseph Doherty
2026-02-28 23:42:47 -05:00
parent f0ea92b8dd
commit 07b494544d
7 changed files with 537 additions and 5 deletions
@@ -0,0 +1,246 @@
using System.Text;
using System.Text.Json;
using System.Linq;
using ZB.MOM.NatsNet.Server.Internal;
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
namespace ZB.MOM.NatsNet.Server;
internal sealed partial class NatsStream
{
internal static (string Ttl, bool Ok) GetMessageScheduleTTL(byte[]? hdr)
{
if (hdr == null || hdr.Length == 0)
return (string.Empty, true);
var ttl = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduleTtl, hdr);
if (ttl == null || ttl.Length == 0)
return (string.Empty, true);
var ttlValue = Encoding.ASCII.GetString(ttl);
var (_, err) = ParseMessageTTL(ttlValue);
return err == null ? (ttlValue, true) : (string.Empty, false);
}
internal static string GetMessageScheduleTarget(byte[]? hdr)
{
if (hdr == null || hdr.Length == 0)
return string.Empty;
var value = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduleTarget, hdr);
return value == null || value.Length == 0 ? string.Empty : Encoding.ASCII.GetString(value);
}
internal static string GetMessageScheduleSource(byte[]? hdr)
{
if (hdr == null || hdr.Length == 0)
return string.Empty;
var value = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduleSource, hdr);
return value == null || value.Length == 0 ? string.Empty : Encoding.ASCII.GetString(value);
}
internal static string GetBatchId(byte[]? hdr)
{
if (hdr == null || hdr.Length == 0)
return string.Empty;
var value = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsBatchId, hdr);
return value == null || value.Length == 0 ? string.Empty : Encoding.ASCII.GetString(value);
}
internal static (ulong Seq, bool Exists) GetBatchSequence(byte[]? hdr)
{
if (hdr == null || hdr.Length == 0)
return (0, false);
var value = NatsMessageHeaders.SliceHeader(NatsHeaderConstants.JsBatchSeq, hdr);
if (value is null || value.Value.Length == 0)
return (0, false);
var parsed = ServerUtilities.ParseInt64(value.Value.Span);
return parsed < 0 ? (0, false) : ((ulong)parsed, true);
}
public bool IsClustered()
{
_mu.EnterReadLock();
try
{
return IsClusteredInternal();
}
finally
{
_mu.ExitReadLock();
}
}
internal bool IsClusteredInternal() => _node is IRaftNode;
internal void QueueInbound(IpQueue<InMsg> inbound, string subject, string? reply, byte[]? hdr, byte[]? msg, object? sourceInfo, object? trace)
{
_ = sourceInfo;
_ = trace;
var inboundMsg = InMsg.Rent();
inboundMsg.Subject = subject;
inboundMsg.Reply = reply;
inboundMsg.Hdr = hdr;
inboundMsg.Msg = msg;
var (_, error) = inbound.Push(inboundMsg);
if (error != null)
inboundMsg.ReturnToPool();
}
internal JsApiMsgGetResponse ProcessDirectGetRequest(string reply, byte[]? hdr, byte[]? msg)
{
var response = new JsApiMsgGetResponse();
if (string.IsNullOrWhiteSpace(reply))
return response;
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiLevelHeader(hdr)))
{
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
return response;
}
if (msg == null || msg.Length == 0)
{
response.Error = JsApiErrors.NewJSBadRequestError();
return response;
}
JsApiMsgGetRequest? request;
try
{
request = JsonSerializer.Deserialize<JsApiMsgGetRequest>(msg);
}
catch (JsonException)
{
response.Error = JsApiErrors.NewJSBadRequestError();
return response;
}
if (request == null)
{
response.Error = JsApiErrors.NewJSBadRequestError();
return response;
}
return GetDirectRequest(request, reply);
}
internal JsApiMsgGetResponse ProcessDirectGetLastBySubjectRequest(string subject, string reply, byte[]? hdr, byte[]? msg)
{
var response = new JsApiMsgGetResponse();
if (string.IsNullOrWhiteSpace(reply))
return response;
if (JetStreamVersioning.ErrorOnRequiredApiLevel(GetRequiredApiLevelHeader(hdr)))
{
response.Error = JsApiErrors.NewJSRequiredApiLevelError();
return response;
}
var request = msg == null || msg.Length == 0
? new JsApiMsgGetRequest()
: JsonSerializer.Deserialize<JsApiMsgGetRequest>(msg) ?? new JsApiMsgGetRequest();
var key = ExtractDirectGetLastBySubjectKey(subject);
if (string.IsNullOrEmpty(key))
{
response.Error = JsApiErrors.NewJSBadRequestError();
return response;
}
request.LastFor = key;
return GetDirectRequest(request, reply);
}
internal JsApiMsgGetResponse GetDirectMulti(JsApiMsgGetRequest request, string reply)
{
_ = reply;
if (Store == null)
return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() };
var filters = request.MultiLastFor ?? [];
if (filters.Length == 0)
return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() };
var (seqs, error) = Store.MultiLastSeqs(filters, request.UpToSeq, 1024);
if (error != null || seqs.Length == 0)
return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() };
var firstSeq = seqs[0];
var loaded = Store.LoadMsg(firstSeq, new StoreMsg());
if (loaded == null)
return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() };
return new JsApiMsgGetResponse { Message = ToStoredMsg(loaded) };
}
internal JsApiMsgGetResponse GetDirectRequest(JsApiMsgGetRequest request, string reply)
{
_ = reply;
if (request.MultiLastFor is { Length: > 0 })
return GetDirectMulti(request, reply);
if (Store == null)
return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() };
StoreMsg? loaded = null;
if (request.Seq > 0)
{
loaded = Store.LoadMsg(request.Seq, new StoreMsg());
}
else if (!string.IsNullOrWhiteSpace(request.LastFor))
{
loaded = Store.LoadLastMsg(request.LastFor!, new StoreMsg());
}
else if (!string.IsNullOrWhiteSpace(request.NextFor))
{
var (sm, _) = Store.LoadNextMsg(request.NextFor!, SubscriptionIndex.SubjectHasWildcard(request.NextFor!), request.Seq, new StoreMsg());
loaded = sm;
}
else if (request.StartTime.HasValue)
{
var seq = Store.GetSeqFromTime(request.StartTime.Value);
if (seq > 0)
loaded = Store.LoadMsg(seq, new StoreMsg());
}
if (loaded == null)
return new JsApiMsgGetResponse { Error = JsApiErrors.NewJSNoMessageFoundError() };
return new JsApiMsgGetResponse { Message = ToStoredMsg(loaded) };
}
private static StoredMsg ToStoredMsg(StoreMsg loaded) => new()
{
Subject = loaded.Subject,
Sequence = loaded.Seq,
Header = loaded.Hdr,
Data = loaded.Msg,
Time = DateTimeOffset.FromUnixTimeMilliseconds(loaded.Ts / 1_000_000L).UtcDateTime,
};
private static string? GetRequiredApiLevelHeader(byte[]? hdr)
{
if (hdr == null || hdr.Length == 0)
return null;
var value = NatsMessageHeaders.GetHeader(JsApiSubjects.JsRequiredApiLevel, hdr);
return value == null || value.Length == 0 ? null : Encoding.ASCII.GetString(value);
}
private static string ExtractDirectGetLastBySubjectKey(string subject)
{
if (string.IsNullOrWhiteSpace(subject))
return string.Empty;
var parts = subject.Split('.');
return parts.Length <= 5 ? string.Empty : string.Join('.', parts.Skip(5));
}
}