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: case "--https_port" when i + 1 < args.Length:
options.MonitorHttpsPort = int.Parse(args[++i]); options.MonitorHttpsPort = int.Parse(args[++i]);
break; 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": case "--tls":
break; break;
case "--tlscert" when i + 1 < args.Length: 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); 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) => Console.CancelKeyPress += (_, e) =>
{ {
e.Cancel = true; e.Cancel = true;
cts.Cancel(); Log.Information("Trapped SIGINT signal");
_ = Task.Run(async () => await server.ShutdownAsync());
}; };
try try
{ {
await server.StartAsync(cts.Token); _ = server.StartAsync(CancellationToken.None);
await server.WaitForReadyAsync();
server.WaitForShutdown();
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {

View File

@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -42,6 +43,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
private int _lameDuck; private int _lameDuck;
private readonly List<PosixSignalRegistration> _signalRegistrations = [];
private string? _portsFilePath; private string? _portsFilePath;
private static readonly TimeSpan AcceptMinSleep = TimeSpan.FromMilliseconds(10); private static readonly TimeSpan AcceptMinSleep = TimeSpan.FromMilliseconds(10);
@@ -187,6 +190,50 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
await ShutdownAsync(); 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) public NatsServer(NatsOptions options, ILoggerFactory loggerFactory)
{ {
_options = options; _options = options;
@@ -561,6 +608,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
{ {
if (!IsShuttingDown) if (!IsShuttingDown)
ShutdownAsync().GetAwaiter().GetResult(); ShutdownAsync().GetAwaiter().GetResult();
foreach (var reg in _signalRegistrations)
reg.Dispose();
_quitCts.Dispose(); _quitCts.Dispose();
_tlsRateLimiter?.Dispose(); _tlsRateLimiter?.Dispose();
_listener?.Dispose(); _listener?.Dispose();