Skip to content

Commit b4a45ab

Browse files
author
Marko Petzold
committed
router/websocket: expose TCP send/recv buffer + Nagle knobs on the server
WebsocketServer gains three optional fields applied to each accepted connection's underlying *net.TCPConn after the WebSocket upgrade: - TCPSendBufferBytes int → SO_SNDBUF - TCPRecvBufferBytes int → SO_RCVBUF - EnableNagle bool → leaves TCP_NODELAY off Defaults are unchanged (zero / false → use OS defaults / NODELAY on), so behavior is preserved for existing deployments. Useful for high-fan-out broker scenarios: Larger SO_SNDBUF reduces the rate at which kernel-side back-pressure parks per-subscriber send goroutines under sustained load — visible in pprof as time spent in syscall.Write returning EAGAIN. Re-enabling Nagle (i.e. NoDelay = false) lets the kernel coalesce successive small writes into fewer TCP packets — fewer syscalls, fewer packets, at the cost of tens of ms added latency per write. Whether this is a win depends on the latency budget; off by default. setsockopt failures are logged but not fatal so a non-TCP transport (e.g. unix-domain sockets via a custom Upgrader) still works.
1 parent 2009c20 commit b4a45ab

1 file changed

Lines changed: 49 additions & 0 deletions

File tree

router/websocketserver.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,24 @@ type WebsocketServer struct {
103103
// client. The default is defaultOutQueueSize.
104104
OutQueueSize int
105105

106+
// TCPSendBufferBytes, when > 0, sets SO_SNDBUF on the underlying TCP
107+
// connection right after the WebSocket upgrade. Larger send buffers
108+
// reduce the rate at which kernel back-pressure stalls per-subscriber
109+
// send goroutines under high fan-out load. 0 = use OS default.
110+
TCPSendBufferBytes int
111+
112+
// TCPRecvBufferBytes, when > 0, sets SO_RCVBUF on the underlying TCP
113+
// connection. 0 = use OS default.
114+
TCPRecvBufferBytes int
115+
116+
// EnableNagle, when true, leaves TCP_NODELAY off (i.e. enables
117+
// Nagle's algorithm). The kernel will then coalesce small successive
118+
// writes into fewer TCP packets — fewer syscalls, fewer packets, but
119+
// added latency (typically tens of ms per write). Off by default;
120+
// enable only for high-throughput broker fan-out scenarios where
121+
// publish-to-subscribe latency budget tolerates the extra delay.
122+
EnableNagle bool
123+
106124
router Router
107125
protocols map[string]protocol
108126
}
@@ -353,9 +371,40 @@ func (s *WebsocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
353371
return
354372
}
355373

374+
s.applyTCPOptions(conn.UnderlyingConn())
375+
356376
s.handleWebsocket(conn, wamp.Dict{"auth": authDict})
357377
}
358378

379+
// applyTCPOptions sets the SO_SNDBUF / SO_RCVBUF / TCP_NODELAY socket
380+
// options on the upgraded connection's underlying TCP conn, per the
381+
// fields on WebsocketServer. Errors are logged and ignored so a setsockopt
382+
// failure (e.g. on a non-TCP transport) doesn't tear down the session.
383+
func (s *WebsocketServer) applyTCPOptions(c net.Conn) {
384+
if c == nil {
385+
return
386+
}
387+
tcp, ok := c.(*net.TCPConn)
388+
if !ok {
389+
return
390+
}
391+
if s.TCPSendBufferBytes > 0 {
392+
if err := tcp.SetWriteBuffer(s.TCPSendBufferBytes); err != nil {
393+
s.router.Logger().Println("SetWriteBuffer:", err)
394+
}
395+
}
396+
if s.TCPRecvBufferBytes > 0 {
397+
if err := tcp.SetReadBuffer(s.TCPRecvBufferBytes); err != nil {
398+
s.router.Logger().Println("SetReadBuffer:", err)
399+
}
400+
}
401+
if s.EnableNagle {
402+
if err := tcp.SetNoDelay(false); err != nil {
403+
s.router.Logger().Println("SetNoDelay(false):", err)
404+
}
405+
}
406+
}
407+
359408
// addProtocol registers a serializer for protocol and payload type.
360409
//
361410
// The Upgrader's own Subprotocols field is intentionally NOT populated here

0 commit comments

Comments
 (0)