From edd566563d0db921b96959ea77474fd12ed578a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Pernas=20Maradei?= Date: Wed, 7 Oct 2026 14:40:03 +0200 Subject: [PATCH] netx: add SetTCPUserTimeout to drop dead TCP connections fast When a peer disappears without closing the connection (e.g. its node loses its network), Linux retries unacknowledged data for ~15 min (tcp_retries2) before the call fails. SetTCPUserTimeout sets TCP_USER_TIMEOUT so the kernel drops the connection after the given time instead. Linux only; a no-op elsewhere. Meant for pgx's AfterNetConnect hook in the services that talk to Postgres. Co-Authored-By: Claude Opus 5.5 --- go.mod | 2 +- netx/tcpusertimeout_linux.go | 36 ++++++++++++++++++++++++++++++++++++ netx/tcpusertimeout_other.go | 14 ++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 netx/tcpusertimeout_linux.go create mode 100644 netx/tcpusertimeout_other.go diff --git a/go.mod b/go.mod index e3f0f32..290f786 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( golang.org/x/crypto v0.50.0 ) -require golang.org/x/sys v0.43.0 // indirect +require golang.org/x/sys v0.43.0 diff --git a/netx/tcpusertimeout_linux.go b/netx/tcpusertimeout_linux.go new file mode 100644 index 0000000..8e304ee --- /dev/null +++ b/netx/tcpusertimeout_linux.go @@ -0,0 +1,36 @@ +//go:build linux + +// Package netx holds network helpers that the standard net package doesn't have. +package netx + +import ( + "crypto/tls" + "net" + "time" + + "golang.org/x/sys/unix" +) + +// SetTCPUserTimeout makes the kernel drop conn when sent data stays unacknowledged for timeout, instead of +// waiting for its ~15 min retry limit (e.g. when the peer's node is gone). Non-TCP connections are left alone. +func SetTCPUserTimeout(conn net.Conn, timeout time.Duration) error { + if tlsConn, ok := conn.(*tls.Conn); ok { + conn = tlsConn.NetConn() + } + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + return nil + } + raw, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var sockErr error + ctrlfn := func(fd uintptr) { + sockErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(timeout.Milliseconds())) + } + if err := raw.Control(ctrlfn); err != nil { + return err + } + return sockErr +} diff --git a/netx/tcpusertimeout_other.go b/netx/tcpusertimeout_other.go new file mode 100644 index 0000000..9a1eafa --- /dev/null +++ b/netx/tcpusertimeout_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +// Package netx holds network helpers that the standard net package doesn't have. +package netx + +import ( + "net" + "time" +) + +// SetTCPUserTimeout does nothing: TCP_USER_TIMEOUT is Linux only. +func SetTCPUserTimeout(net.Conn, time.Duration) error { + return nil +}