feat: execute post-baseline jetstream parity plan

This commit is contained in:
Joseph Doherty
2026-02-23 12:11:19 -05:00
parent c3763e83d6
commit b41e6ff320
58 changed files with 1430 additions and 102 deletions

View File

@@ -93,6 +93,8 @@ public sealed class StreamManager
{
if (!_streams.TryGetValue(name, out var stream))
return false;
if (stream.Config.Sealed || stream.Config.DenyPurge)
return false;
stream.Store.PurgeAsync(default).GetAwaiter().GetResult();
return true;
@@ -110,6 +112,8 @@ public sealed class StreamManager
{
if (!_streams.TryGetValue(name, out var stream))
return false;
if (stream.Config.Sealed || stream.Config.DenyDelete)
return false;
return stream.Store.RemoveAsync(sequence, default).GetAwaiter().GetResult();
}
@@ -156,6 +160,17 @@ public sealed class StreamManager
if (stream == null)
return null;
if (stream.Config.MaxMsgSize > 0 && payload.Length > stream.Config.MaxMsgSize)
{
return new PubAck
{
Stream = stream.Config.Name,
ErrorCode = 10054,
};
}
PruneExpiredMessages(stream, DateTime.UtcNow);
var stateBefore = stream.Store.GetStateAsync(default).GetAwaiter().GetResult();
if (stream.Config.MaxBytes > 0 && (long)stateBefore.Bytes + payload.Length > stream.Config.MaxBytes)
{
@@ -179,7 +194,7 @@ public sealed class StreamManager
_ = replicaGroup.ProposeAsync($"PUB {subject}", default).GetAwaiter().GetResult();
var seq = stream.Store.AppendAsync(subject, payload, default).GetAwaiter().GetResult();
EnforceLimits(stream);
EnforceRuntimePolicies(stream, DateTime.UtcNow);
var stored = stream.Store.LoadAsync(seq, default).GetAwaiter().GetResult();
if (stored != null)
ReplicateIfConfigured(stream.Config.Name, stored);
@@ -209,14 +224,25 @@ public sealed class StreamManager
MaxBytes = config.MaxBytes,
MaxMsgsPer = config.MaxMsgsPer,
MaxAgeMs = config.MaxAgeMs,
MaxMsgSize = config.MaxMsgSize,
MaxConsumers = config.MaxConsumers,
DuplicateWindowMs = config.DuplicateWindowMs,
Sealed = config.Sealed,
DenyDelete = config.DenyDelete,
DenyPurge = config.DenyPurge,
AllowDirect = config.AllowDirect,
Retention = config.Retention,
Discard = config.Discard,
Storage = config.Storage,
Replicas = config.Replicas,
Mirror = config.Mirror,
Source = config.Source,
Sources = config.Sources.Count == 0 ? [] : [.. config.Sources.Select(s => new StreamSourceConfig { Name = s.Name })],
Sources = config.Sources.Count == 0 ? [] : [.. config.Sources.Select(s => new StreamSourceConfig
{
Name = s.Name,
SubjectTransformPrefix = s.SubjectTransformPrefix,
SourceAccount = s.SourceAccount,
})],
};
return copy;
@@ -235,6 +261,13 @@ public sealed class StreamManager
};
}
private static void EnforceRuntimePolicies(StreamHandle stream, DateTime nowUtc)
{
EnforceLimits(stream);
PrunePerSubject(stream);
PruneExpiredMessages(stream, nowUtc);
}
private static void EnforceLimits(StreamHandle stream)
{
if (stream.Config.MaxMsgs <= 0)
@@ -251,6 +284,34 @@ public sealed class StreamManager
fileStore.TrimToMaxMessages(maxMessages);
}
private static void PrunePerSubject(StreamHandle stream)
{
if (stream.Config.MaxMsgsPer <= 0)
return;
var maxPerSubject = stream.Config.MaxMsgsPer;
var messages = stream.Store.ListAsync(default).GetAwaiter().GetResult();
foreach (var group in messages.GroupBy(m => m.Subject, StringComparer.Ordinal))
{
foreach (var message in group.OrderByDescending(m => m.Sequence).Skip(maxPerSubject))
stream.Store.RemoveAsync(message.Sequence, default).GetAwaiter().GetResult();
}
}
private static void PruneExpiredMessages(StreamHandle stream, DateTime nowUtc)
{
if (stream.Config.MaxAgeMs <= 0)
return;
var cutoff = nowUtc.AddMilliseconds(-stream.Config.MaxAgeMs);
var messages = stream.Store.ListAsync(default).GetAwaiter().GetResult();
foreach (var message in messages)
{
if (message.TimestampUtc < cutoff)
stream.Store.RemoveAsync(message.Sequence, default).GetAwaiter().GetResult();
}
}
private void RebuildReplicationCoordinators()
{
_mirrorsByOrigin.Clear();
@@ -269,7 +330,7 @@ public sealed class StreamManager
&& _streams.TryGetValue(stream.Config.Source, out _))
{
var list = _sourcesByOrigin.GetOrAdd(stream.Config.Source, _ => []);
list.Add(new SourceCoordinator(stream.Store));
list.Add(new SourceCoordinator(stream.Store, new StreamSourceConfig { Name = stream.Config.Source }));
}
if (stream.Config.Sources.Count > 0)
@@ -280,7 +341,7 @@ public sealed class StreamManager
continue;
var list = _sourcesByOrigin.GetOrAdd(source.Name, _ => []);
list.Add(new SourceCoordinator(stream.Store));
list.Add(new SourceCoordinator(stream.Store, source));
}
}
}
@@ -320,6 +381,7 @@ public sealed class StreamManager
StorageType.File => new FileStore(new FileStoreOptions
{
Directory = Path.Combine(Path.GetTempPath(), "natsdotnet-js-store", config.Name),
MaxAgeMs = config.MaxAgeMs,
}),
_ => new MemStore(),
};