feat: upgrade JetStreamService to lifecycle orchestrator
Implements enableJetStream() semantics from golang/nats-server/server/jetstream.go:414-523. - JetStreamService.StartAsync(): validates config, creates store directory (including nested paths via Directory.CreateDirectory), registers all $JS.API.> subjects, logs startup stats; idempotent on double-start - JetStreamService.DisposeAsync(): clears registered subjects, marks not running - New properties: RegisteredApiSubjects, MaxStreams, MaxConsumers, MaxMemory, MaxStore - JetStreamOptions: adds MaxStreams and MaxConsumers limits (0 = unlimited) - FileStoreConfig: removes duplicate StoreCipher/StoreCompression enum declarations now that AeadEncryptor.cs owns them; updates defaults to NoCipher/NoCompression - FileStoreOptions/FileStore: align enum member names with AeadEncryptor.cs (NoCipher, NoCompression, S2Compression) to fix cross-task naming conflict - 13 new tests in JetStreamServiceOrchestrationTests covering all lifecycle paths
This commit is contained in:
@@ -1,29 +1,148 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NATS.Server.Configuration;
|
||||
using NATS.Server;
|
||||
using NATS.Server.JetStream.Api;
|
||||
|
||||
namespace NATS.Server.JetStream;
|
||||
|
||||
// Maps to Go's enableJetStream() in server/jetstream.go:414-523.
|
||||
// Orchestrates the JetStream subsystem lifecycle: validates config, creates the
|
||||
// store directory, registers API subjects, and tears down cleanly on dispose.
|
||||
public sealed class JetStreamService : IAsyncDisposable
|
||||
{
|
||||
// Full set of $JS.API.> subjects registered at startup.
|
||||
// Mirrors the subjects registered by setJetStreamExportSubs() in
|
||||
// golang/nats-server/server/jetstream.go and jsApiSubs in jetstream_api.go.
|
||||
private static readonly IReadOnlyList<string> AllApiSubjects =
|
||||
[
|
||||
"$JS.API.>",
|
||||
JetStreamApiSubjects.Info,
|
||||
JetStreamApiSubjects.StreamCreate + "*",
|
||||
JetStreamApiSubjects.StreamUpdate + "*",
|
||||
JetStreamApiSubjects.StreamDelete + "*",
|
||||
JetStreamApiSubjects.StreamInfo + "*",
|
||||
JetStreamApiSubjects.StreamNames,
|
||||
JetStreamApiSubjects.StreamList,
|
||||
JetStreamApiSubjects.StreamPurge + "*",
|
||||
JetStreamApiSubjects.StreamMessageGet + "*",
|
||||
JetStreamApiSubjects.StreamMessageDelete + "*",
|
||||
JetStreamApiSubjects.StreamSnapshot + "*",
|
||||
JetStreamApiSubjects.StreamRestore + "*",
|
||||
JetStreamApiSubjects.StreamLeaderStepdown + "*",
|
||||
JetStreamApiSubjects.ConsumerCreate + "*",
|
||||
JetStreamApiSubjects.ConsumerDelete + "*.*",
|
||||
JetStreamApiSubjects.ConsumerInfo + "*.*",
|
||||
JetStreamApiSubjects.ConsumerNames + "*",
|
||||
JetStreamApiSubjects.ConsumerList + "*",
|
||||
JetStreamApiSubjects.ConsumerPause + "*.*",
|
||||
JetStreamApiSubjects.ConsumerNext + "*.*",
|
||||
JetStreamApiSubjects.DirectGet + "*",
|
||||
JetStreamApiSubjects.MetaLeaderStepdown,
|
||||
];
|
||||
|
||||
private readonly JetStreamOptions _options;
|
||||
private readonly ILogger<JetStreamService> _logger;
|
||||
private List<string> _registeredApiSubjects = [];
|
||||
|
||||
public InternalClient? InternalClient { get; }
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The API subjects registered with the server after a successful StartAsync.
|
||||
/// Empty before start or after dispose.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> RegisteredApiSubjects => _registeredApiSubjects;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum streams limit from configuration. 0 means unlimited.
|
||||
/// Maps to Go's JetStreamAccountLimits.MaxStreams.
|
||||
/// </summary>
|
||||
public int MaxStreams => _options.MaxStreams;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum consumers limit from configuration. 0 means unlimited.
|
||||
/// Maps to Go's JetStreamAccountLimits.MaxConsumers.
|
||||
/// </summary>
|
||||
public int MaxConsumers => _options.MaxConsumers;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum memory store bytes from configuration. 0 means unlimited.
|
||||
/// Maps to Go's JetStreamConfig.MaxMemory.
|
||||
/// </summary>
|
||||
public long MaxMemory => _options.MaxMemoryStore;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum file store bytes from configuration. 0 means unlimited.
|
||||
/// Maps to Go's JetStreamConfig.MaxStore.
|
||||
/// </summary>
|
||||
public long MaxStore => _options.MaxFileStore;
|
||||
|
||||
public JetStreamService(JetStreamOptions options, InternalClient? internalClient = null)
|
||||
: this(options, internalClient, NullLoggerFactory.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public JetStreamService(JetStreamOptions options, InternalClient? internalClient, ILoggerFactory loggerFactory)
|
||||
{
|
||||
_options = options;
|
||||
InternalClient = internalClient;
|
||||
_logger = loggerFactory.CreateLogger<JetStreamService>();
|
||||
}
|
||||
|
||||
// Maps to Go's enableJetStream() in server/jetstream.go:414-523.
|
||||
// Validates the store directory, creates it if absent, then registers all
|
||||
// $JS.API.> subjects so inbound API messages can be routed.
|
||||
public Task StartAsync(CancellationToken ct)
|
||||
{
|
||||
if (IsRunning)
|
||||
{
|
||||
_logger.LogDebug("JetStream is already running; ignoring duplicate StartAsync");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Validate and create store directory when specified.
|
||||
// Go: os.MkdirAll(cfg.StoreDir, defaultDirPerms) — jetstream.go:430-444.
|
||||
if (!string.IsNullOrEmpty(_options.StoreDir))
|
||||
{
|
||||
if (Directory.Exists(_options.StoreDir))
|
||||
{
|
||||
_logger.LogDebug("JetStream store directory already exists: {StoreDir}", _options.StoreDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(_options.StoreDir);
|
||||
_logger.LogInformation("JetStream store directory created: {StoreDir}", _options.StoreDir);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("JetStream running in memory-only mode (no StoreDir configured)");
|
||||
}
|
||||
|
||||
// Register all $JS.API.> subjects.
|
||||
// Go: setJetStreamExportSubs() — jetstream.go:489-494.
|
||||
_registeredApiSubjects = [.. AllApiSubjects];
|
||||
|
||||
IsRunning = true;
|
||||
|
||||
_logger.LogInformation(
|
||||
"JetStream started. MaxMemory={MaxMemory}, MaxStore={MaxStore}, MaxStreams={MaxStreams}, MaxConsumers={MaxConsumers}, RegisteredSubjects={Count}",
|
||||
_options.MaxMemoryStore,
|
||||
_options.MaxFileStore,
|
||||
_options.MaxStreams,
|
||||
_options.MaxConsumers,
|
||||
_registeredApiSubjects.Count);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Maps to Go's shutdown path in jetstream.go.
|
||||
// Clears registered subjects and marks the service as not running.
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_registeredApiSubjects = [];
|
||||
IsRunning = false;
|
||||
_logger.LogInformation("JetStream stopped");
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user