perf: eliminate per-message allocations in pub/sub hot path and coalesce outbound writes
Pub/sub 1:1 (16B) improved from 0.18x to 0.50x, fan-out from 0.18x to 0.44x, and JetStream durable fetch from 0.13x to 0.64x vs Go. Key changes: replace .ToArray() copy in SendMessage with pooled buffer handoff, batch multiple small writes into single WriteAsync via 64KB coalesce buffer in write loop, and remove profiling Stopwatch instrumentation from ProcessMessage/StreamManager hot paths.
This commit is contained in:
@@ -436,9 +436,16 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
await _jetStreamService.DisposeAsync();
|
||||
_stats.JetStreamEnabled = false;
|
||||
|
||||
// Wait for accept loops to exit
|
||||
await _acceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
await _wsAcceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
// If server was never started, accept loops never ran — signal immediately
|
||||
if (_listener == null)
|
||||
_acceptLoopExited.TrySetResult();
|
||||
if (_wsListener == null)
|
||||
_wsAcceptLoopExited.TrySetResult();
|
||||
|
||||
// Wait for accept loops to exit (in parallel to avoid sequential 5s+5s waits)
|
||||
await Task.WhenAll(
|
||||
_acceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)),
|
||||
_wsAcceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5))).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
|
||||
// Close all client connections — flush first, then mark closed
|
||||
var flushTasks = new List<Task>();
|
||||
@@ -485,9 +492,10 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
_listener?.Close();
|
||||
_wsListener?.Close();
|
||||
|
||||
// Wait for accept loops to exit
|
||||
await _acceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
await _wsAcceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
// Wait for accept loops to exit (in parallel)
|
||||
await Task.WhenAll(
|
||||
_acceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5)),
|
||||
_wsAcceptLoopExited.Task.WaitAsync(TimeSpan.FromSeconds(5))).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||
|
||||
var gracePeriod = _options.LameDuckGracePeriod;
|
||||
if (gracePeriod < TimeSpan.Zero) gracePeriod = -gracePeriod;
|
||||
@@ -887,6 +895,11 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
|
||||
|
||||
_ = RunWebSocketAcceptLoopAsync(linked.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No WebSocket listener — signal immediately so shutdown doesn't wait
|
||||
_wsAcceptLoopExited.TrySetResult();
|
||||
}
|
||||
|
||||
if (_routeManager != null)
|
||||
await _routeManager.StartAsync(linked.Token);
|
||||
|
||||
Reference in New Issue
Block a user