grpc/grpc-go · error
error getting option on socket: %v
Error message
error getting option on socket: %v
What it means
GetTCPUserTimeout reads the TCP_USER_TIMEOUT socket option via GetsockoptInt inside a rawConn.Control() callback. This error fires when the Control() call itself fails (the underlying syscall returned an errno), meaning gRPC could not query the kernel-level keepalive timeout that the OS has set on this connection's file descriptor. It is produced by gRPC's internal syscall package, not by application code.
Source
Thrown at internal/syscall/syscall_linux.go:107
}
// GetTCPUserTimeout gets the TCP user timeout on a connection's socket
func GetTCPUserTimeout(conn net.Conn) (opt int, err error) {
tcpconn, ok := conn.(*net.TCPConn)
if !ok {
err = fmt.Errorf("conn is not *net.TCPConn. got %T", conn)
return
}
rawConn, err := tcpconn.SyscallConn()
if err != nil {
err = fmt.Errorf("error getting raw connection: %v", err)
return
}
err = rawConn.Control(func(fd uintptr) {
opt, err = syscall.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT)
})
if err != nil {
err = fmt.Errorf("error getting option on socket: %v", err)
return
}
return
}
View on GitHub (pinned to 03255a9237)
Solutions
- Check whether the connection is still open before retrying — this error often surfaces during shutdown when fds are reclaimed
- Upgrade to a Linux kernel >= 2.6.37 that supports TCP_USER_TIMEOUT
- If seeing this in logs only, it is likely benign noise from a connection teardown race — verify gRPC keepalive behavior is functioning via higher-level connectivity callbacks
- If reproducing consistently on a custom dialer, ensure the dialer returns a standard *net.TCPConn
Example fix
// before: custom dialer returns a wrapped connection
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return wrappedConn{...}, nil
})
// after: ensure a standard *net.TCPConn is returned so GetTCPUserTimeout works
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
d := net.Dialer{}
return d.DialContext(ctx, "tcp", addr)
}) Defensive patterns
Strategy: validation
Validate before calling
// Before calling APIs that depend on TCP socket options, verify the connection is a live TCP conn:
func isLiveTCPConn(conn net.Conn) bool {
tc, ok := conn.(*net.TCPConn)
if !ok {
return false
}
// Attempt SyscallConn to verify fd is valid
raw, err := tc.SyscallConn()
if err != nil {
return false
}
_ = raw // fd is valid if no error
return true
} Try / catch
// GetTCPUserTimeout is internal; handle the caller gracefully:
if timeout, err := syscall.GetTCPUserTimeout(conn); err != nil {
// Connection may be closed or socket unsupported; fall back to default keepalive
logger.Warningf("could not read TCP_USER_TIMEOUT: %v, using default keepalive", err)
} else {
// use timeout
} Prevention
- Ensure connections passed to gRPC are standard *net.TCPConn from net.Dialer, not custom wrappers
- Run on Linux kernels >= 2.6.37 for TCP_USER_TIMEOUT support
- Handle connection lifecycle carefully to avoid races between close and socket option reads
- This is an internal gRPC call — no user action needed unless you directly call GetTCPUserTimeout
When it happens
Trigger: Called internally by gRPC on a *net.TCPConn when it needs to read the current TCP_USER_TIMEOUT value (e.g., during connection health checks or keepalive log dumps). Fires when rawConn.Control() returns a non-nil error — typically EBADF (closed fd), EINVAL, or a similar kernel-level errno on the underlying socket file descriptor.
Common situations: The connection has been closed or its file descriptor invalidated before gRPC attempts to read the option; running on a kernel that does not support TCP_USER_TIMEOUT (older Linux < 2.6.37); the conn passed is not a real TCP socket despite passing the earlier type assertion; connection races during shutdown where the fd is reclaimed between SyscallConn() and Control().
Related errors
- error getting raw connection: %v
- error setting option on socket: %v
- conn is not *net.TCPConn. got %T
- keepalive ping not acked within timeout %s
- last resolver error: %v
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/f0a4b5bf639ed750.
Report an issue: GitHub.