Add SubscriptionIndex factory methods, notification wrappers, and ValidateMapping. Implement 24 MemStore methods (TTL, scheduling, SDM, age-check, purge/compact/reset) with JetStream header helpers and constants. Verified features: 987 → 1026.
137 lines
5.1 KiB
C#
137 lines
5.1 KiB
C#
// Copyright 2025 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// Adapted from server/sdm.go in the NATS server Go source.
|
|
|
|
namespace ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
|
|
|
/// <summary>
|
|
/// Per-sequence data tracked by <see cref="StreamDeletionMeta"/>.
|
|
/// Mirrors <c>SDMBySeq</c> in server/sdm.go.
|
|
/// </summary>
|
|
public readonly struct SdmBySeq
|
|
{
|
|
/// <summary>Whether this sequence was the last message for its subject.</summary>
|
|
public bool Last { get; init; }
|
|
|
|
/// <summary>Timestamp (nanoseconds UTC) when the removal/SDM was last proposed.</summary>
|
|
public long Ts { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tracks pending subject delete markers (SDMs) and message removals for a stream.
|
|
/// Used by JetStream cluster consensus to avoid redundant proposals.
|
|
/// Mirrors <c>SDMMeta</c> in server/sdm.go.
|
|
/// </summary>
|
|
public sealed class StreamDeletionMeta
|
|
{
|
|
// Per-subject pending-count totals.
|
|
private readonly Dictionary<string, ulong> _totals = new(1);
|
|
|
|
// Per-sequence data keyed by sequence number.
|
|
private readonly Dictionary<ulong, SdmBySeq> _pending = new(1);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Header constants (forward-declared; populated in session 19 — JetStream).
|
|
// isSubjectDeleteMarker checks these header keys.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Mirrors JSMarkerReason header key (defined in jetstream.go).
|
|
internal const string HeaderJsMarkerReason = "Nats-Marker-Reason";
|
|
|
|
// Mirrors KVOperation header key (defined in jetstream.go).
|
|
internal const string HeaderKvOperation = "KV-Operation";
|
|
|
|
// Mirrors KVOperationValuePurge (defined in jetstream.go).
|
|
internal const string KvOperationValuePurge = "PURGE";
|
|
|
|
/// <summary>
|
|
/// Returns true when the given header block contains a subject delete marker
|
|
/// (either a JetStream marker or a KV purge operation).
|
|
/// Mirrors <c>isSubjectDeleteMarker</c> in server/sdm.go.
|
|
/// </summary>
|
|
public static bool IsSubjectDeleteMarker(ReadOnlySpan<byte> hdr)
|
|
{
|
|
// Simplified header scan: checks whether JSMarkerReason key is present
|
|
// or whether KV-Operation equals "PURGE".
|
|
// Full implementation depends on SliceHeader from session 08 (client.go).
|
|
// Until then this provides the correct contract.
|
|
var text = System.Text.Encoding.UTF8.GetString(hdr);
|
|
if (text.Contains(HeaderJsMarkerReason))
|
|
return true;
|
|
if (text.Contains($"{HeaderKvOperation}: {KvOperationValuePurge}"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get the pending entry for <paramref name="seq"/>.
|
|
/// </summary>
|
|
public bool TryGetPending(ulong seq, out SdmBySeq entry) => _pending.TryGetValue(seq, out entry);
|
|
|
|
/// <summary>
|
|
/// Sets the pending entry for <paramref name="seq"/>.
|
|
/// </summary>
|
|
public void SetPending(ulong seq, SdmBySeq entry) => _pending[seq] = entry;
|
|
|
|
/// <summary>
|
|
/// Returns the pending count for <paramref name="subj"/>, or 0 if not tracked.
|
|
/// </summary>
|
|
public ulong GetSubjectTotal(string subj) => _totals.TryGetValue(subj, out var cnt) ? cnt : 0;
|
|
|
|
/// <summary>
|
|
/// Clears all tracked data.
|
|
/// Mirrors <c>SDMMeta.empty</c>.
|
|
/// </summary>
|
|
public void Empty()
|
|
{
|
|
_totals.Clear();
|
|
_pending.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tracks <paramref name="seq"/> as pending and returns whether it was
|
|
/// the last message for its subject. If the sequence is already tracked
|
|
/// the existing <c>Last</c> value is returned without modification.
|
|
/// Mirrors <c>SDMMeta.trackPending</c>.
|
|
/// </summary>
|
|
public bool TrackPending(ulong seq, string subj, bool last)
|
|
{
|
|
if (_pending.TryGetValue(seq, out var p))
|
|
return p.Last;
|
|
|
|
_pending[seq] = new SdmBySeq { Last = last, Ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L };
|
|
_totals[subj] = _totals.TryGetValue(subj, out var cnt) ? cnt + 1 : 1;
|
|
return last;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes <paramref name="seq"/> and decrements the pending count for
|
|
/// <paramref name="subj"/>, deleting the subject entry when it reaches zero.
|
|
/// Mirrors <c>SDMMeta.removeSeqAndSubject</c>.
|
|
/// </summary>
|
|
public void RemoveSeqAndSubject(ulong seq, string subj)
|
|
{
|
|
if (!_pending.Remove(seq))
|
|
return;
|
|
|
|
if (_totals.TryGetValue(subj, out var msgs))
|
|
{
|
|
if (msgs <= 1)
|
|
_totals.Remove(subj);
|
|
else
|
|
_totals[subj] = msgs - 1;
|
|
}
|
|
}
|
|
}
|