Merge branch 'codex/jetstream-full-parity-executeplan' into main
# Conflicts: # differences.md # docs/plans/2026-02-23-jetstream-full-parity-plan.md # src/NATS.Server/Auth/Account.cs # src/NATS.Server/Configuration/ConfigProcessor.cs # src/NATS.Server/Monitoring/VarzHandler.cs # src/NATS.Server/NatsClient.cs # src/NATS.Server/NatsOptions.cs # src/NATS.Server/NatsServer.cs
This commit is contained in:
@@ -10,9 +10,15 @@ using NATS.NKeys;
|
||||
using NATS.Server.Auth;
|
||||
using NATS.Server.Configuration;
|
||||
using NATS.Server.Events;
|
||||
using NATS.Server.Gateways;
|
||||
using NATS.Server.Imports;
|
||||
using NATS.Server.JetStream;
|
||||
using NATS.Server.JetStream.Api;
|
||||
using NATS.Server.JetStream.Publish;
|
||||
using NATS.Server.LeafNodes;
|
||||
using NATS.Server.Monitoring;
|
||||
using NATS.Server.Protocol;
|
||||
using NATS.Server.Routes;
|
||||
using NATS.Server.Subscriptions;
|
||||
using NATS.Server.Tls;
|
||||
using NATS.Server.WebSocket;
|
||||
@@ -42,6 +48,14 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
private readonly SslServerAuthenticationOptions? _sslOptions;
|
||||
private readonly TlsRateLimiter? _tlsRateLimiter;
|
||||
private readonly SubjectTransform[] _subjectTransforms;
|
||||
private readonly RouteManager? _routeManager;
|
||||
private readonly GatewayManager? _gatewayManager;
|
||||
private readonly LeafNodeManager? _leafNodeManager;
|
||||
private readonly JetStreamService? _jetStreamService;
|
||||
private readonly JetStreamApiRouter? _jetStreamApiRouter;
|
||||
private readonly StreamManager? _jetStreamStreamManager;
|
||||
private readonly ConsumerManager? _jetStreamConsumerManager;
|
||||
private readonly JetStreamPublisher? _jetStreamPublisher;
|
||||
private Socket? _listener;
|
||||
private Socket? _wsListener;
|
||||
private readonly TaskCompletionSource _wsAcceptLoopExited = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
@@ -79,12 +93,37 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
public InternalEventSystem? EventSystem => _eventSystem;
|
||||
public bool IsShuttingDown => Volatile.Read(ref _shutdown) != 0;
|
||||
public bool IsLameDuckMode => Volatile.Read(ref _lameDuck) != 0;
|
||||
public string? ClusterListen => _routeManager?.ListenEndpoint;
|
||||
public JetStreamApiRouter? JetStreamApiRouter => _jetStreamApiRouter;
|
||||
public int JetStreamStreams => _jetStreamStreamManager?.StreamNames.Count ?? 0;
|
||||
public int JetStreamConsumers => _jetStreamConsumerManager?.ConsumerCount ?? 0;
|
||||
public Action? ReOpenLogFile { get; set; }
|
||||
public IEnumerable<NatsClient> GetClients() => _clients.Values;
|
||||
|
||||
public IEnumerable<ClosedClient> GetClosedClients() => _closedClients;
|
||||
|
||||
public IEnumerable<Auth.Account> GetAccounts() => _accounts.Values;
|
||||
public bool HasRemoteInterest(string subject) => _globalAccount.SubList.HasRemoteInterest(subject);
|
||||
public bool TryCaptureJetStreamPublish(string subject, ReadOnlyMemory<byte> payload, out PubAck ack)
|
||||
{
|
||||
if (_jetStreamPublisher != null && _jetStreamPublisher.TryCapture(subject, payload, out ack))
|
||||
{
|
||||
if (ack.ErrorCode == null
|
||||
&& _jetStreamConsumerManager != null
|
||||
&& _jetStreamStreamManager != null
|
||||
&& _jetStreamStreamManager.TryGet(ack.Stream, out var streamHandle))
|
||||
{
|
||||
var stored = streamHandle.Store.LoadAsync(ack.Seq, default).GetAwaiter().GetResult();
|
||||
if (stored != null)
|
||||
_jetStreamConsumerManager.OnPublished(ack.Stream, stored);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ack = new PubAck();
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task WaitForReadyAsync() => _listeningStarted.Task;
|
||||
|
||||
@@ -118,6 +157,15 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
// Close listeners to stop accept loops
|
||||
_listener?.Close();
|
||||
_wsListener?.Close();
|
||||
if (_routeManager != null)
|
||||
await _routeManager.DisposeAsync();
|
||||
if (_gatewayManager != null)
|
||||
await _gatewayManager.DisposeAsync();
|
||||
if (_leafNodeManager != null)
|
||||
await _leafNodeManager.DisposeAsync();
|
||||
if (_jetStreamService != null)
|
||||
await _jetStreamService.DisposeAsync();
|
||||
_stats.JetStreamEnabled = false;
|
||||
|
||||
// Wait for accept loops to exit
|
||||
await _acceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
@@ -314,6 +362,33 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
AuthRequired = _authService.IsAuthRequired,
|
||||
};
|
||||
|
||||
if (options.Cluster != null)
|
||||
{
|
||||
_routeManager = new RouteManager(options.Cluster, _stats, _serverInfo.ServerId, ApplyRemoteSubscription,
|
||||
_loggerFactory.CreateLogger<RouteManager>());
|
||||
}
|
||||
|
||||
if (options.Gateway != null)
|
||||
{
|
||||
_gatewayManager = new GatewayManager(options.Gateway, _stats,
|
||||
_loggerFactory.CreateLogger<GatewayManager>());
|
||||
}
|
||||
|
||||
if (options.LeafNode != null)
|
||||
{
|
||||
_leafNodeManager = new LeafNodeManager(options.LeafNode, _stats,
|
||||
_loggerFactory.CreateLogger<LeafNodeManager>());
|
||||
}
|
||||
|
||||
if (options.JetStream != null)
|
||||
{
|
||||
_jetStreamStreamManager = new StreamManager();
|
||||
_jetStreamConsumerManager = new ConsumerManager();
|
||||
_jetStreamService = new JetStreamService(options.JetStream);
|
||||
_jetStreamApiRouter = new JetStreamApiRouter(_jetStreamStreamManager, _jetStreamConsumerManager);
|
||||
_jetStreamPublisher = new JetStreamPublisher(_jetStreamStreamManager);
|
||||
}
|
||||
|
||||
if (options.HasTls)
|
||||
{
|
||||
_sslOptions = TlsHelper.BuildServerAuthOptions(options);
|
||||
@@ -441,6 +516,18 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
_ = RunWebSocketAcceptLoopAsync(linked.Token);
|
||||
}
|
||||
|
||||
if (_routeManager != null)
|
||||
await _routeManager.StartAsync(linked.Token);
|
||||
if (_gatewayManager != null)
|
||||
await _gatewayManager.StartAsync(linked.Token);
|
||||
if (_leafNodeManager != null)
|
||||
await _leafNodeManager.StartAsync(linked.Token);
|
||||
if (_jetStreamService != null)
|
||||
{
|
||||
await _jetStreamService.StartAsync(linked.Token);
|
||||
_stats.JetStreamEnabled = true;
|
||||
}
|
||||
|
||||
_listeningStarted.TrySetResult();
|
||||
|
||||
_eventSystem?.Start(this);
|
||||
@@ -705,9 +792,22 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public void OnLocalSubscription(string subject, string? queue)
|
||||
{
|
||||
_routeManager?.PropagateLocalSubscription(subject, queue);
|
||||
}
|
||||
|
||||
private void ApplyRemoteSubscription(RemoteSubscription sub)
|
||||
{
|
||||
_globalAccount.SubList.ApplyRemoteSub(sub);
|
||||
}
|
||||
|
||||
public void ProcessMessage(string subject, string? replyTo, ReadOnlyMemory<byte> headers,
|
||||
ReadOnlyMemory<byte> payload, NatsClient sender)
|
||||
{
|
||||
if (TryCaptureJetStreamPublish(subject, payload, out var pubAck))
|
||||
sender.RecordJetStreamPubAck(pubAck);
|
||||
|
||||
// Apply subject transforms
|
||||
if (_subjectTransforms.Length > 0)
|
||||
{
|
||||
@@ -1310,10 +1410,22 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
/// the changes, and applies reloadable settings. CLI overrides are preserved.
|
||||
/// </summary>
|
||||
public void ReloadConfig()
|
||||
{
|
||||
ReloadConfigCore(throwOnError: false);
|
||||
}
|
||||
|
||||
public void ReloadConfigOrThrow()
|
||||
{
|
||||
ReloadConfigCore(throwOnError: true);
|
||||
}
|
||||
|
||||
private void ReloadConfigCore(bool throwOnError)
|
||||
{
|
||||
if (_options.ConfigFile == null)
|
||||
{
|
||||
_logger.LogWarning("No config file specified, cannot reload");
|
||||
if (throwOnError)
|
||||
throw new InvalidOperationException("No config file specified.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1339,6 +1451,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
{
|
||||
foreach (var err in errors)
|
||||
_logger.LogError("Config reload error: {Error}", err);
|
||||
if (throwOnError)
|
||||
throw new InvalidOperationException(string.Join("; ", errors));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1350,6 +1464,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to reload config file: {ConfigFile}", _options.ConfigFile);
|
||||
if (throwOnError)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1454,6 +1570,11 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
_tlsRateLimiter?.Dispose();
|
||||
_listener?.Dispose();
|
||||
_wsListener?.Dispose();
|
||||
_routeManager?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
_gatewayManager?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
_leafNodeManager?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
_jetStreamService?.DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
_stats.JetStreamEnabled = false;
|
||||
foreach (var client in _clients.Values)
|
||||
client.Dispose();
|
||||
foreach (var account in _accounts.Values)
|
||||
|
||||
Reference in New Issue
Block a user