260 lines
6.3 KiB
C#
260 lines
6.3 KiB
C#
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);
|
|
}
|
|
}
|