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
149 lines
5.7 KiB
C#
149 lines
5.7 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Server.Configuration;
|
|
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;
|
|
}
|
|
}
|