diff --git a/src/NATS.Server/Monitoring/Varz.cs b/src/NATS.Server/Monitoring/Varz.cs
index 847bdc2..3e85374 100644
--- a/src/NATS.Server/Monitoring/Varz.cs
+++ b/src/NATS.Server/Monitoring/Varz.cs
@@ -157,6 +157,12 @@ public sealed class Varz
[JsonPropertyName("slow_consumer_stats")]
public SlowConsumersStats SlowConsumerStats { get; set; } = new();
+ [JsonPropertyName("stale_connections")]
+ public long StaleConnections { get; set; }
+
+ [JsonPropertyName("stale_connection_stats")]
+ public StaleConnectionStats StaleConnectionStatsDetail { get; set; } = new();
+
[JsonPropertyName("subscriptions")]
public uint Subscriptions { get; set; }
@@ -219,6 +225,25 @@ public sealed class SlowConsumersStats
public ulong Leafs { get; set; }
}
+///
+/// Statistics about stale connections by connection type.
+/// Corresponds to Go server/monitor.go StaleConnectionStats struct.
+///
+public sealed class StaleConnectionStats
+{
+ [JsonPropertyName("clients")]
+ public ulong Clients { get; set; }
+
+ [JsonPropertyName("routes")]
+ public ulong Routes { get; set; }
+
+ [JsonPropertyName("gateways")]
+ public ulong Gateways { get; set; }
+
+ [JsonPropertyName("leafs")]
+ public ulong Leafs { get; set; }
+}
+
///
/// Cluster configuration monitoring information.
/// Corresponds to Go server/monitor.go ClusterOptsVarz struct.
diff --git a/src/NATS.Server/Monitoring/VarzHandler.cs b/src/NATS.Server/Monitoring/VarzHandler.cs
index c872b28..433aa84 100644
--- a/src/NATS.Server/Monitoring/VarzHandler.cs
+++ b/src/NATS.Server/Monitoring/VarzHandler.cs
@@ -91,6 +91,14 @@ public sealed class VarzHandler : IDisposable
Gateways = (ulong)Interlocked.Read(ref stats.SlowConsumerGateways),
Leafs = (ulong)Interlocked.Read(ref stats.SlowConsumerLeafs),
},
+ StaleConnections = Interlocked.Read(ref stats.StaleConnections),
+ StaleConnectionStatsDetail = new StaleConnectionStats
+ {
+ Clients = (ulong)Interlocked.Read(ref stats.StaleConnectionClients),
+ Routes = (ulong)Interlocked.Read(ref stats.StaleConnectionRoutes),
+ Gateways = (ulong)Interlocked.Read(ref stats.StaleConnectionGateways),
+ Leafs = (ulong)Interlocked.Read(ref stats.StaleConnectionLeafs),
+ },
Subscriptions = _server.SubList.Count,
ConfigLoadTime = _server.StartTime,
HttpReqStats = stats.HttpReqStats.ToDictionary(kv => kv.Key, kv => (ulong)kv.Value),
diff --git a/src/NATS.Server/NatsClient.cs b/src/NATS.Server/NatsClient.cs
index a2bb19e..b568536 100644
--- a/src/NATS.Server/NatsClient.cs
+++ b/src/NATS.Server/NatsClient.cs
@@ -682,6 +682,12 @@ public sealed class NatsClient : IDisposable
break;
}
+ if (reason == ClientClosedReason.StaleConnection)
+ {
+ Interlocked.Increment(ref _serverStats.StaleConnections);
+ Interlocked.Increment(ref _serverStats.StaleConnectionClients);
+ }
+
_logger.LogDebug("Client {ClientId} connection closed: {CloseReason}", Id, reason);
}
diff --git a/src/NATS.Server/ServerStats.cs b/src/NATS.Server/ServerStats.cs
index b737dee..21ebd7d 100644
--- a/src/NATS.Server/ServerStats.cs
+++ b/src/NATS.Server/ServerStats.cs
@@ -16,5 +16,9 @@ public sealed class ServerStats
public long SlowConsumerRoutes;
public long SlowConsumerLeafs;
public long SlowConsumerGateways;
+ public long StaleConnectionClients;
+ public long StaleConnectionRoutes;
+ public long StaleConnectionLeafs;
+ public long StaleConnectionGateways;
public readonly ConcurrentDictionary HttpReqStats = new();
}
diff --git a/tests/NATS.Server.Tests/ServerStatsTests.cs b/tests/NATS.Server.Tests/ServerStatsTests.cs
index 6b45bd3..7baf061 100644
--- a/tests/NATS.Server.Tests/ServerStatsTests.cs
+++ b/tests/NATS.Server.Tests/ServerStatsTests.cs
@@ -75,6 +75,27 @@ public class ServerStatsTests : IAsyncLifetime
client.StartTime.ShouldNotBe(default);
}
+ [Fact]
+ public void StaleConnection_stats_incremented_on_mark_closed()
+ {
+ var stats = new ServerStats();
+ stats.StaleConnectionClients.ShouldBe(0);
+
+ Interlocked.Increment(ref stats.StaleConnectionClients);
+ stats.StaleConnectionClients.ShouldBe(1);
+ }
+
+ [Fact]
+ public void StaleConnection_stats_all_fields_default_to_zero()
+ {
+ var stats = new ServerStats();
+ stats.StaleConnections.ShouldBe(0);
+ stats.StaleConnectionClients.ShouldBe(0);
+ stats.StaleConnectionRoutes.ShouldBe(0);
+ stats.StaleConnectionLeafs.ShouldBe(0);
+ stats.StaleConnectionGateways.ShouldBe(0);
+ }
+
private static int GetFreePort()
{
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);