feat(config): add SIGHUP signal handler and config reload validation

Implements SignalHandler (PosixSignalRegistration for SIGHUP) and
ReloadFromOptionsAsync/ReloadFromOptionsResult on ConfigReloader for
in-memory options comparison without reading a config file.
Ports Go server/signal_unix.go handleSignals and server/reload.go Reload.
This commit is contained in:
Joseph Doherty
2026-02-25 02:54:13 -05:00
parent b7bac8e68e
commit e09835ca70
3 changed files with 131 additions and 0 deletions

View File

@@ -398,6 +398,30 @@ public static class ConfigReloader
}, ct);
}
/// <summary>
/// Compares two options directly (without reading from a config file) and returns
/// a reload result indicating whether the change is valid.
/// Go reference: server/reload.go — Reload with in-memory options comparison.
/// </summary>
public static Task<ReloadFromOptionsResult> ReloadFromOptionsAsync(NatsOptions original, NatsOptions updated)
{
var changes = Diff(original, updated);
var errors = Validate(changes);
var rejectedChanges = new List<string>();
foreach (var change in changes)
{
if (change.IsNonReloadable)
{
rejectedChanges.Add($"{change.Name} cannot be changed at runtime");
}
}
return Task.FromResult(new ReloadFromOptionsResult(
Success: rejectedChanges.Count == 0 && errors.Count == 0,
RejectedChanges: rejectedChanges));
}
// ─── Comparison helpers ─────────────────────────────────────────
private static void CompareAndAdd<T>(List<IConfigChange> changes, string name, T oldVal, T newVal)
@@ -524,3 +548,8 @@ public sealed class ConfigReloadResult
public bool HasErrors => Errors is { Count: > 0 };
}
/// <summary>
/// Result of an in-memory options comparison for reload validation.
/// </summary>
public sealed record ReloadFromOptionsResult(bool Success, List<string> RejectedChanges);

View File

@@ -0,0 +1,41 @@
using System.Runtime.InteropServices;
namespace NATS.Server.Configuration;
/// <summary>
/// Registers POSIX signal handlers for config reload.
/// Go reference: server/signal_unix.go, server/opts.go reload logic.
/// On SIGHUP, triggers config reload via ConfigReloader.
/// </summary>
public static class SignalHandler
{
private static PosixSignalRegistration? _registration;
/// <summary>
/// Registers a SIGHUP handler that will call the provided reload callback.
/// Go reference: server/signal_unix.go — handleSignals goroutine.
/// </summary>
/// <param name="onReload">Callback invoked when SIGHUP is received.</param>
public static void Register(Action onReload)
{
ArgumentNullException.ThrowIfNull(onReload);
_registration = PosixSignalRegistration.Create(PosixSignal.SIGHUP, _ =>
{
onReload();
});
}
/// <summary>
/// Unregisters the SIGHUP handler.
/// </summary>
public static void Unregister()
{
_registration?.Dispose();
_registration = null;
}
/// <summary>
/// Whether a SIGHUP handler is currently registered.
/// </summary>
public static bool IsRegistered => _registration is not null;
}