feat: execute full-repo remaining parity closure plan
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user