feat: add signal handling (SIGTERM, SIGUSR2, SIGHUP) and CLI stubs

This commit is contained in:
Joseph Doherty
2026-02-22 23:52:49 -05:00
parent e57605f090
commit df39ebdc58
2 changed files with 68 additions and 4 deletions

View File

@@ -32,6 +32,15 @@ for (int i = 0; i < args.Length; i++)
case "--https_port" when i + 1 < args.Length:
options.MonitorHttpsPort = int.Parse(args[++i]);
break;
case "-c" when i + 1 < args.Length:
options.ConfigFile = args[++i];
break;
case "--pid" when i + 1 < args.Length:
options.PidFile = args[++i];
break;
case "--ports_file_dir" when i + 1 < args.Length:
options.PortsFileDir = args[++i];
break;
case "--tls":
break;
case "--tlscert" when i + 1 < args.Length:
@@ -50,18 +59,24 @@ for (int i = 0; i < args.Length; i++)
}
using var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(Log.Logger);
var server = new NatsServer(options, loggerFactory);
using var server = new NatsServer(options, loggerFactory);
var cts = new CancellationTokenSource();
// Register signal handlers
server.HandleSignals();
// Ctrl+C triggers graceful shutdown
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
Log.Information("Trapped SIGINT signal");
_ = Task.Run(async () => await server.ShutdownAsync());
};
try
{
await server.StartAsync(cts.Token);
_ = server.StartAsync(CancellationToken.None);
await server.WaitForReadyAsync();
server.WaitForShutdown();
}
catch (OperationCanceledException)
{

View File

@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.Extensions.Logging;
@@ -42,6 +43,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
private int _lameDuck;
private readonly List<PosixSignalRegistration> _signalRegistrations = [];
private string? _portsFilePath;
private static readonly TimeSpan AcceptMinSleep = TimeSpan.FromMilliseconds(10);
@@ -187,6 +190,50 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
await ShutdownAsync();
}
/// <summary>
/// Registers Unix signal handlers.
/// SIGTERM → shutdown, SIGUSR2 → lame duck, SIGUSR1 → log reopen (stub), SIGHUP → reload (stub).
/// </summary>
public void HandleSignals()
{
_signalRegistrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGTERM, ctx =>
{
ctx.Cancel = true;
_logger.LogInformation("Trapped SIGTERM signal");
_ = Task.Run(async () => await ShutdownAsync());
}));
_signalRegistrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGQUIT, ctx =>
{
ctx.Cancel = true;
_logger.LogInformation("Trapped SIGQUIT signal");
_ = Task.Run(async () => await ShutdownAsync());
}));
_signalRegistrations.Add(PosixSignalRegistration.Create(PosixSignal.SIGHUP, ctx =>
{
ctx.Cancel = true;
_logger.LogWarning("Trapped SIGHUP signal — config reload not yet supported");
}));
// SIGUSR1 and SIGUSR2 only on non-Windows
if (!OperatingSystem.IsWindows())
{
_signalRegistrations.Add(PosixSignalRegistration.Create((PosixSignal)10, ctx =>
{
ctx.Cancel = true;
_logger.LogWarning("Trapped SIGUSR1 signal — log reopen not yet supported");
}));
_signalRegistrations.Add(PosixSignalRegistration.Create((PosixSignal)12, ctx =>
{
ctx.Cancel = true;
_logger.LogInformation("Trapped SIGUSR2 signal — entering lame duck mode");
_ = Task.Run(async () => await LameDuckShutdownAsync());
}));
}
}
public NatsServer(NatsOptions options, ILoggerFactory loggerFactory)
{
_options = options;
@@ -561,6 +608,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
{
if (!IsShuttingDown)
ShutdownAsync().GetAwaiter().GetResult();
foreach (var reg in _signalRegistrations)
reg.Dispose();
_quitCts.Dispose();
_tlsRateLimiter?.Dispose();
_listener?.Dispose();