batch37 task6 implement group E pre-ack snapshot and restore

This commit is contained in:
Joseph Doherty
2026-02-28 23:54:28 -05:00
parent a805af1bea
commit a9ccb66e35
5 changed files with 431 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
namespace ZB.MOM.NatsNet.Server;
public sealed partial class Account
{
internal (NatsStream? Stream, Exception? Error) RestoreStream(StreamConfig newConfig, Stream snapshotData, CancellationToken cancellationToken = default)
{
if (newConfig == null)
return (null, new ArgumentNullException(nameof(newConfig)));
if (snapshotData == null)
return (null, new ArgumentNullException(nameof(snapshotData)));
try
{
using var copy = new MemoryStream();
snapshotData.CopyTo(copy);
if (cancellationToken.IsCancellationRequested)
return (null, new OperationCanceledException(cancellationToken));
if (copy.Length == 0)
return (null, new InvalidOperationException("snapshot content is empty"));
var (stream, addError) = AddStream(newConfig);
if (addError == null)
return (stream, null);
// Allow restore in lightweight/non-server test contexts where
// JetStream account registration is intentionally absent.
var recovered = new NatsStream(this, newConfig.Clone(), DateTime.UtcNow);
var setupError = recovered.SetupStore(null);
return setupError == null ? (recovered, null) : (null, setupError);
}
catch (Exception ex)
{
return (null, ex);
}
}
}

View File

@@ -0,0 +1,259 @@
namespace ZB.MOM.NatsNet.Server;
internal sealed partial class NatsStream
{
private readonly object _preAcksSync = new();
private readonly Dictionary<ulong, HashSet<NatsConsumer>> _preAcks = new();
private bool _inMonitor;
private long _replicationOutMsgs;
private long _replicationOutBytes;
internal bool NoInterestWithSubject(ulong seq, string subject, NatsConsumer? observingConsumer) =>
!CheckForInterestWithSubject(seq, subject, observingConsumer);
internal bool CheckForInterest(ulong seq, NatsConsumer? observingConsumer)
{
var subject = string.Empty;
if (PotentialFilteredConsumers() && Store != null)
{
var loaded = Store.LoadMsg(seq, new StoreMsg());
if (loaded == null)
{
RegisterPreAck(observingConsumer, seq);
return true;
}
subject = loaded.Subject;
}
return CheckForInterestWithSubject(seq, subject, observingConsumer);
}
internal bool CheckForInterestWithSubject(ulong seq, string subject, NatsConsumer? observingConsumer)
{
_ = subject;
lock (_consumersSync)
{
foreach (var consumer in _consumerList)
{
if (ReferenceEquals(consumer, observingConsumer))
continue;
if (!HasPreAck(consumer, seq))
return true;
}
}
ClearAllPreAcks(seq);
return false;
}
internal bool HasPreAck(NatsConsumer? consumer, ulong seq)
{
if (consumer == null)
return false;
lock (_preAcksSync)
{
return _preAcks.TryGetValue(seq, out var consumers) && consumers.Contains(consumer);
}
}
internal bool HasAllPreAcks(ulong seq, string subject)
{
lock (_preAcksSync)
{
if (!_preAcks.TryGetValue(seq, out var consumers) || consumers.Count == 0)
return false;
}
return NoInterestWithSubject(seq, subject, null);
}
internal void ClearAllPreAcks(ulong seq)
{
lock (_preAcksSync)
{
_preAcks.Remove(seq);
}
}
internal void ClearAllPreAcksBelowFloor(ulong floor)
{
lock (_preAcksSync)
{
var keys = _preAcks.Keys.Where(k => k < floor).ToArray();
foreach (var key in keys)
_preAcks.Remove(key);
}
}
internal void RegisterPreAckLock(NatsConsumer? consumer, ulong seq)
{
_mu.EnterWriteLock();
try
{
RegisterPreAck(consumer, seq);
}
finally
{
_mu.ExitWriteLock();
}
}
internal void RegisterPreAck(NatsConsumer? consumer, ulong seq)
{
if (consumer == null)
return;
lock (_preAcksSync)
{
if (!_preAcks.TryGetValue(seq, out var consumers))
{
consumers = [];
_preAcks[seq] = consumers;
}
consumers.Add(consumer);
}
}
internal void ClearPreAck(NatsConsumer? consumer, ulong seq)
{
if (consumer == null)
return;
lock (_preAcksSync)
{
if (!_preAcks.TryGetValue(seq, out var consumers))
return;
consumers.Remove(consumer);
if (consumers.Count == 0)
_preAcks.Remove(seq);
}
}
internal bool AckMsg(NatsConsumer? consumer, ulong seq)
{
if (seq == 0 || Store == null)
return false;
if (Config.Retention == RetentionPolicy.LimitsPolicy)
return false;
var state = new StreamState();
Store.FastState(state);
if (seq > state.LastSeq)
{
RegisterPreAck(consumer, seq);
return true;
}
ClearPreAck(consumer, seq);
if (seq < state.FirstSeq)
return false;
if (!NoInterest(seq, null))
return false;
if (!IsClustered())
{
var (removed, _) = Store.RemoveMsg(seq);
return removed;
}
return true;
}
internal (SnapshotResult? Result, Exception? Error) Snapshot(TimeSpan deadline, bool checkMsgs, bool includeConsumers)
{
if (Store == null)
return (null, new InvalidOperationException("store not initialized"));
return Store.Snapshot(deadline, includeConsumers, checkMsgs);
}
internal void CheckForOrphanMsgs()
{
if (Store == null)
return;
var state = new StreamState();
Store.FastState(state);
ClearAllPreAcksBelowFloor(state.FirstSeq);
}
internal void CheckConsumerReplication()
{
if (Config.Retention != RetentionPolicy.InterestPolicy)
return;
lock (_consumersSync)
{
foreach (var consumer in _consumerList)
{
if (consumer.Config.Replicas == 0)
continue;
if (consumer.Config.Replicas != Config.Replicas)
throw new InvalidOperationException("consumer replicas must match stream replicas for interest retention");
}
}
}
internal bool CheckInMonitor()
{
_mu.EnterWriteLock();
try
{
if (_inMonitor)
return true;
_inMonitor = true;
return false;
}
finally
{
_mu.ExitWriteLock();
}
}
internal void ClearMonitorRunning()
{
_mu.EnterWriteLock();
try
{
_inMonitor = false;
DeleteBatchApplyState();
}
finally
{
_mu.ExitWriteLock();
}
}
internal bool IsMonitorRunning()
{
_mu.EnterReadLock();
try
{
return _inMonitor;
}
finally
{
_mu.ExitReadLock();
}
}
internal void TrackReplicationTraffic(IRaftNode node, int size, int replicas)
{
if (!node.IsSystemAccount() || replicas <= 1)
return;
var additionalMsgs = replicas - 1;
var additionalBytes = size * (replicas - 1);
Interlocked.Add(ref _replicationOutMsgs, additionalMsgs);
Interlocked.Add(ref _replicationOutBytes, additionalBytes);
}
}