Merge pull request #196 from DarkWolfCave/fix/ghost-connections

Add TCP keepalive and write deadline on accepted connections
This commit is contained in:
Shizun Ge
2026-03-17 15:47:54 -07:00
committed by GitHub
+21
View File
@@ -54,6 +54,23 @@ func NewClient(conn net.Conn, interval time.Duration, maxClients int64) *Client
time.Sleep(interval)
}
atomic.AddInt64(&numCurrentClients, 1)
// Enable TCP keepalive to detect dead peers at the kernel level.
// This is a safety net for long intervals where the write deadline
// alone would be too slow to detect dead connections. For example,
// with interval=10min, without keepalive a ghost connection would
// go undetected for 10+ minutes.
// Keepalive cannot detect peers that are alive at the TCP level
// (kernel still responds to probes) but stalled at the application
// level — the write deadline in Send() covers that case.
// Detection time: ~idle + 9 probes (e.g. 10s + 9×10s ≈ 100s on Linux).
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
keepalive := interval
if keepalive > 30*time.Second {
keepalive = 30 * time.Second
}
tcpConn.SetKeepAlivePeriod(keepalive)
}
addr := conn.RemoteAddr().(*net.TCPAddr)
glog.V(1).Infof("ACCEPT host=%v port=%v n=%v/%v\n", addr.IP, addr.Port, numCurrentClients, maxClients)
return &Client{
@@ -80,6 +97,10 @@ func (c *Client) Send(bannerMaxLength int64) (int, error) {
}
c.next = time.Now().Add(c.interval)
length := rand.Int63n(bannerMaxLength)
// Set a write deadline to detect dead connections where the kernel
// buffers data but the remote peer is gone. Without this, Write()
// can succeed indefinitely on dead connections, causing goroutine leaks.
c.conn.SetWriteDeadline(time.Now().Add(c.interval))
bytesSent, err := c.conn.Write(randStringBytes(length))
if err != nil {
return 0, err