feat: add PID file and ports file support
This commit is contained in:
@@ -42,10 +42,7 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
|||||||
|
|
||||||
private int _lameDuck;
|
private int _lameDuck;
|
||||||
|
|
||||||
// Used by future ports file implementation
|
|
||||||
#pragma warning disable CS0169 // Field is never used
|
|
||||||
private string? _portsFilePath;
|
private string? _portsFilePath;
|
||||||
#pragma warning restore CS0169
|
|
||||||
|
|
||||||
private static readonly TimeSpan AcceptMinSleep = TimeSpan.FromMilliseconds(10);
|
private static readonly TimeSpan AcceptMinSleep = TimeSpan.FromMilliseconds(10);
|
||||||
private static readonly TimeSpan AcceptMaxSleep = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan AcceptMaxSleep = TimeSpan.FromSeconds(1);
|
||||||
@@ -108,6 +105,9 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
|||||||
if (_monitorServer != null)
|
if (_monitorServer != null)
|
||||||
await _monitorServer.DisposeAsync();
|
await _monitorServer.DisposeAsync();
|
||||||
|
|
||||||
|
DeletePidFile();
|
||||||
|
DeletePortsFile();
|
||||||
|
|
||||||
_logger.LogInformation("Server Exiting..");
|
_logger.LogInformation("Server Exiting..");
|
||||||
_shutdownComplete.TrySetResult();
|
_shutdownComplete.TrySetResult();
|
||||||
}
|
}
|
||||||
@@ -263,6 +263,9 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
|||||||
await _monitorServer.StartAsync(linked.Token);
|
await _monitorServer.StartAsync(linked.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WritePidFile();
|
||||||
|
WritePortsFile();
|
||||||
|
|
||||||
var tmpDelay = AcceptMinSleep;
|
var tmpDelay = AcceptMinSleep;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -492,6 +495,68 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
|||||||
client.Account?.RemoveClient(client.Id);
|
client.Account?.RemoveClient(client.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WritePidFile()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_options.PidFile)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(_options.PidFile, Environment.ProcessId.ToString());
|
||||||
|
_logger.LogDebug("Wrote PID file {PidFile}", _options.PidFile);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error writing PID file {PidFile}", _options.PidFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeletePidFile()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_options.PidFile)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(_options.PidFile))
|
||||||
|
File.Delete(_options.PidFile);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error deleting PID file {PidFile}", _options.PidFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WritePortsFile()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_options.PortsFileDir)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exeName = Path.GetFileNameWithoutExtension(Environment.ProcessPath ?? "nats-server");
|
||||||
|
var fileName = $"{exeName}_{Environment.ProcessId}.ports";
|
||||||
|
_portsFilePath = Path.Combine(_options.PortsFileDir, fileName);
|
||||||
|
|
||||||
|
var ports = new { client = _options.Port, monitor = _options.MonitorPort > 0 ? _options.MonitorPort : (int?)null };
|
||||||
|
var json = System.Text.Json.JsonSerializer.Serialize(ports);
|
||||||
|
File.WriteAllText(_portsFilePath, json);
|
||||||
|
_logger.LogDebug("Wrote ports file {PortsFile}", _portsFilePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error writing ports file to {PortsFileDir}", _options.PortsFileDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeletePortsFile()
|
||||||
|
{
|
||||||
|
if (_portsFilePath == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(_portsFilePath))
|
||||||
|
File.Delete(_portsFilePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error deleting ports file {PortsFile}", _portsFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!IsShuttingDown)
|
if (!IsShuttingDown)
|
||||||
|
|||||||
@@ -842,3 +842,62 @@ public class LameDuckTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PidFileTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _tempDir = Path.Combine(Path.GetTempPath(), $"nats-test-{Guid.NewGuid():N}");
|
||||||
|
|
||||||
|
public PidFileTests() => Directory.CreateDirectory(_tempDir);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_tempDir))
|
||||||
|
Directory.Delete(_tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetFreePort()
|
||||||
|
{
|
||||||
|
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
sock.Bind(new IPEndPoint(IPAddress.Loopback, 0));
|
||||||
|
return ((IPEndPoint)sock.LocalEndPoint!).Port;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Server_writes_pid_file_on_startup()
|
||||||
|
{
|
||||||
|
var pidFile = Path.Combine(_tempDir, "nats.pid");
|
||||||
|
var port = GetFreePort();
|
||||||
|
var server = new NatsServer(new NatsOptions { Port = port, PidFile = pidFile }, NullLoggerFactory.Instance);
|
||||||
|
_ = server.StartAsync(CancellationToken.None);
|
||||||
|
await server.WaitForReadyAsync();
|
||||||
|
|
||||||
|
File.Exists(pidFile).ShouldBeTrue();
|
||||||
|
var content = await File.ReadAllTextAsync(pidFile);
|
||||||
|
int.Parse(content).ShouldBe(Environment.ProcessId);
|
||||||
|
|
||||||
|
await server.ShutdownAsync();
|
||||||
|
File.Exists(pidFile).ShouldBeFalse();
|
||||||
|
|
||||||
|
server.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Server_writes_ports_file_on_startup()
|
||||||
|
{
|
||||||
|
var port = GetFreePort();
|
||||||
|
var server = new NatsServer(new NatsOptions { Port = port, PortsFileDir = _tempDir }, NullLoggerFactory.Instance);
|
||||||
|
_ = server.StartAsync(CancellationToken.None);
|
||||||
|
await server.WaitForReadyAsync();
|
||||||
|
|
||||||
|
var portsFiles = Directory.GetFiles(_tempDir, "*.ports");
|
||||||
|
portsFiles.Length.ShouldBe(1);
|
||||||
|
|
||||||
|
var content = await File.ReadAllTextAsync(portsFiles[0]);
|
||||||
|
content.ShouldContain($"\"client\":{port}");
|
||||||
|
|
||||||
|
await server.ShutdownAsync();
|
||||||
|
Directory.GetFiles(_tempDir, "*.ports").Length.ShouldBe(0);
|
||||||
|
|
||||||
|
server.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user