{"record":{"id":"f0a4b5bf639ed750","repo":"grpc/grpc-go","slug":"error-getting-option-on-socket-v","errorCode":null,"errorMessage":"error getting option on socket: %v","messagePattern":"error getting option on socket: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/syscall/syscall_linux.go","lineNumber":107,"sourceCode":"}\n\n// GetTCPUserTimeout gets the TCP user timeout on a connection's socket\nfunc GetTCPUserTimeout(conn net.Conn) (opt int, err error) {\n\ttcpconn, ok := conn.(*net.TCPConn)\n\tif !ok {\n\t\terr = fmt.Errorf(\"conn is not *net.TCPConn. got %T\", conn)\n\t\treturn\n\t}\n\trawConn, err := tcpconn.SyscallConn()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error getting raw connection: %v\", err)\n\t\treturn\n\t}\n\terr = rawConn.Control(func(fd uintptr) {\n\t\topt, err = syscall.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT)\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error getting option on socket: %v\", err)\n\t\treturn\n\t}\n\n\treturn\n}\n","sourceCodeStart":89,"sourceCodeEnd":113,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/syscall/syscall_linux.go#L89-L113","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","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"],"exampleFix":"// before: custom dialer returns a wrapped connection\ngrpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {\n    return wrappedConn{...}, nil\n})\n// after: ensure a standard *net.TCPConn is returned so GetTCPUserTimeout works\ngrpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {\n    d := net.Dialer{}\n    return d.DialContext(ctx, \"tcp\", addr)\n})","handlingStrategy":"validation","validationCode":"// Before calling APIs that depend on TCP socket options, verify the connection is a live TCP conn:\nfunc isLiveTCPConn(conn net.Conn) bool {\n    tc, ok := conn.(*net.TCPConn)\n    if !ok {\n        return false\n    }\n    // Attempt SyscallConn to verify fd is valid\n    raw, err := tc.SyscallConn()\n    if err != nil {\n        return false\n    }\n    _ = raw // fd is valid if no error\n    return true\n}","typeGuard":null,"tryCatchPattern":"// GetTCPUserTimeout is internal; handle the caller gracefully:\nif timeout, err := syscall.GetTCPUserTimeout(conn); err != nil {\n    // Connection may be closed or socket unsupported; fall back to default keepalive\n    logger.Warningf(\"could not read TCP_USER_TIMEOUT: %v, using default keepalive\", err)\n} else {\n    // use timeout\n}","preventionTips":["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"],"tags":["network","tcp","syscall","linux","keepalive","socket"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}