feat: execute full-repo remaining parity closure plan

This commit is contained in:
Joseph Doherty
2026-02-23 13:08:52 -05:00
parent cbe1fa6121
commit 2b64d762f6
75 changed files with 2325 additions and 121 deletions

View File

@@ -1,3 +1,5 @@
using System.Text;
namespace NATS.Server.Subscriptions;
/// <summary>
@@ -13,6 +15,7 @@ public sealed class SubList : IDisposable
private readonly ReaderWriterLockSlim _lock = new();
private readonly TrieLevel _root = new();
private readonly SubListCacheSweeper _sweeper = new();
private readonly Dictionary<string, RemoteSubscription> _remoteSubs = new(StringComparer.Ordinal);
private Dictionary<string, CachedResult>? _cache = new(StringComparer.Ordinal);
private uint _count;
@@ -22,9 +25,12 @@ public sealed class SubList : IDisposable
private ulong _cacheHits;
private ulong _inserts;
private ulong _removes;
private int _highFanoutNodes;
private readonly record struct CachedResult(SubListResult Result, long Generation);
public event Action<InterestChange>? InterestChanged;
public void Dispose()
{
_disposed = true;
@@ -97,6 +103,10 @@ public sealed class SubList : IDisposable
}
}
internal int HighFanoutNodeCountForTest => Volatile.Read(ref _highFanoutNodes);
internal Task TriggerCacheSweepAsyncForTest() => _sweeper.TriggerSweepAsync(SweepCache);
public void ApplyRemoteSub(RemoteSubscription sub)
{
_lock.EnterWriteLock();
@@ -104,9 +114,23 @@ public sealed class SubList : IDisposable
{
var key = $"{sub.RouteId}|{sub.Account}|{sub.Subject}|{sub.Queue}";
if (sub.IsRemoval)
{
_remoteSubs.Remove(key);
InterestChanged?.Invoke(new InterestChange(
InterestChangeKind.RemoteRemoved,
sub.Subject,
sub.Queue,
sub.Account));
}
else
{
_remoteSubs[key] = sub;
InterestChanged?.Invoke(new InterestChange(
InterestChangeKind.RemoteAdded,
sub.Subject,
sub.Queue,
sub.Account));
}
Interlocked.Increment(ref _generation);
}
finally
@@ -187,6 +211,11 @@ public sealed class SubList : IDisposable
if (sub.Queue == null)
{
node.PlainSubs.Add(sub);
if (!node.PackedListEnabled && node.PlainSubs.Count > 256)
{
node.PackedListEnabled = true;
Interlocked.Increment(ref _highFanoutNodes);
}
}
else
{
@@ -201,6 +230,11 @@ public sealed class SubList : IDisposable
_count++;
_inserts++;
Interlocked.Increment(ref _generation);
InterestChanged?.Invoke(new InterestChange(
InterestChangeKind.LocalAdded,
sub.Subject,
sub.Queue,
sub.Client?.Account?.Name ?? "$G"));
}
finally
{
@@ -218,6 +252,11 @@ public sealed class SubList : IDisposable
{
_removes++;
Interlocked.Increment(ref _generation);
InterestChanged?.Invoke(new InterestChange(
InterestChangeKind.LocalRemoved,
sub.Subject,
sub.Queue,
sub.Client?.Account?.Name ?? "$G"));
}
}
finally
@@ -362,11 +401,7 @@ public sealed class SubList : IDisposable
{
_cache[subject] = new CachedResult(result, currentGen);
if (_cache.Count > CacheMax)
{
var keys = _cache.Keys.Take(_cache.Count - CacheSweep).ToList();
foreach (var key in keys)
_cache.Remove(key);
}
_sweeper.ScheduleSweep(SweepCache);
}
return result;
@@ -377,6 +412,58 @@ public sealed class SubList : IDisposable
}
}
public SubListResult MatchBytes(ReadOnlySpan<byte> subjectUtf8)
{
return Match(Encoding.ASCII.GetString(subjectUtf8));
}
public IReadOnlyList<RemoteSubscription> MatchRemote(string account, string subject)
{
_lock.EnterReadLock();
try
{
var expanded = new List<RemoteSubscription>();
foreach (var remoteSub in _remoteSubs.Values)
{
if (remoteSub.IsRemoval)
continue;
if (!string.Equals(remoteSub.Account, account, StringComparison.Ordinal))
continue;
if (!SubjectMatch.MatchLiteral(subject, remoteSub.Subject))
continue;
var weight = Math.Max(1, remoteSub.QueueWeight);
for (var i = 0; i < weight; i++)
expanded.Add(remoteSub);
}
return expanded;
}
finally
{
_lock.ExitReadLock();
}
}
private void SweepCache()
{
_lock.EnterWriteLock();
try
{
if (_cache == null || _cache.Count <= CacheMax)
return;
var removeCount = Math.Min(CacheSweep, _cache.Count - CacheMax);
var keys = _cache.Keys.Take(removeCount).ToArray();
foreach (var key in keys)
_cache.Remove(key);
}
finally
{
_lock.ExitWriteLock();
}
}
/// <summary>
/// Tokenize the subject into an array of token strings.
/// Returns null if the subject is invalid (empty tokens).
@@ -879,6 +966,7 @@ public sealed class SubList : IDisposable
public TrieLevel? Next;
public readonly HashSet<Subscription> PlainSubs = [];
public readonly Dictionary<string, HashSet<Subscription>> QueueSubs = new(StringComparer.Ordinal);
public bool PackedListEnabled;
public bool IsEmpty => PlainSubs.Count == 0 && QueueSubs.Count == 0 &&
(Next == null || (Next.Nodes.Count == 0 && Next.Pwc == null && Next.Fwc == null));