diff --git a/src/NATS.Server/Monitoring/MonitorServer.cs b/src/NATS.Server/Monitoring/MonitorServer.cs index fd2335d..f33dbff 100644 --- a/src/NATS.Server/Monitoring/MonitorServer.cs +++ b/src/NATS.Server/Monitoring/MonitorServer.cs @@ -13,6 +13,7 @@ public sealed class MonitorServer : IAsyncDisposable { private readonly WebApplication _app; private readonly ILogger _logger; + private readonly VarzHandler _varzHandler; public MonitorServer(NatsServer server, NatsOptions options, ServerStats stats, ILoggerFactory loggerFactory) { @@ -25,7 +26,7 @@ public sealed class MonitorServer : IAsyncDisposable _app = builder.Build(); var basePath = options.MonitorBasePath ?? ""; - var varzHandler = new VarzHandler(server, options); + _varzHandler = new VarzHandler(server, options); _app.MapGet(basePath + "/", () => { @@ -44,10 +45,10 @@ public sealed class MonitorServer : IAsyncDisposable stats.HttpReqStats.AddOrUpdate("/healthz", 1, (_, v) => v + 1); return Results.Ok("ok"); }); - _app.MapGet(basePath + "/varz", async () => + _app.MapGet(basePath + "/varz", async (HttpContext ctx) => { stats.HttpReqStats.AddOrUpdate("/varz", 1, (_, v) => v + 1); - return Results.Ok(await varzHandler.HandleVarzAsync()); + return Results.Ok(await _varzHandler.HandleVarzAsync(ctx.RequestAborted)); }); // Stubs for unimplemented endpoints @@ -106,6 +107,8 @@ public sealed class MonitorServer : IAsyncDisposable public async ValueTask DisposeAsync() { + await _app.StopAsync(); await _app.DisposeAsync(); + _varzHandler.Dispose(); } } diff --git a/src/NATS.Server/Monitoring/VarzHandler.cs b/src/NATS.Server/Monitoring/VarzHandler.cs index 9a2aff5..036fb92 100644 --- a/src/NATS.Server/Monitoring/VarzHandler.cs +++ b/src/NATS.Server/Monitoring/VarzHandler.cs @@ -8,7 +8,7 @@ namespace NATS.Server.Monitoring; /// Handles building the Varz response from server state and process metrics. /// Corresponds to Go server/monitor.go handleVarz function. /// -public sealed class VarzHandler +public sealed class VarzHandler : IDisposable { private readonly NatsServer _server; private readonly NatsOptions _options; @@ -21,17 +21,17 @@ public sealed class VarzHandler { _server = server; _options = options; - var proc = Process.GetCurrentProcess(); + using var proc = Process.GetCurrentProcess(); _lastCpuSampleTime = DateTime.UtcNow; _lastCpuUsage = proc.TotalProcessorTime; } - public async Task HandleVarzAsync() + public async Task HandleVarzAsync(CancellationToken ct = default) { - await _varzMu.WaitAsync(); + await _varzMu.WaitAsync(ct); try { - var proc = Process.GetCurrentProcess(); + using var proc = Process.GetCurrentProcess(); var now = DateTime.UtcNow; var uptime = now - _server.StartTime; var stats = _server.Stats; @@ -100,6 +100,11 @@ public sealed class VarzHandler } } + public void Dispose() + { + _varzMu.Dispose(); + } + /// /// Formats a TimeSpan as a human-readable uptime string matching Go server format. /// diff --git a/tests/NATS.Server.Tests/MonitorTests.cs b/tests/NATS.Server.Tests/MonitorTests.cs index 73caef5..0d4c52e 100644 --- a/tests/NATS.Server.Tests/MonitorTests.cs +++ b/tests/NATS.Server.Tests/MonitorTests.cs @@ -27,8 +27,17 @@ public class MonitorTests : IAsyncLifetime { _ = _server.StartAsync(_cts.Token); await _server.WaitForReadyAsync(); - // Give monitoring server time to start - await Task.Delay(200); + // Wait for monitoring HTTP server to be ready + for (int i = 0; i < 50; i++) + { + try + { + var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/healthz"); + if (response.IsSuccessStatusCode) break; + } + catch (HttpRequestException) { } + await Task.Delay(50); + } } public async Task DisposeAsync()