tailscale/tailscale · error
unable to set read deadline: %w
Error message
unable to set read deadline: %w
What it means
The reader goroutine accepts the looped-back TCP connection and, when the probe context has a deadline, applies it with readConn.SetReadDeadline so the read cannot outlive the probe. This error fires when setting that deadline fails — almost always because the connection is already closed, or the platform rejects the deadline value.
Source
Thrown at prober/derp.go:1090
// This goroutine reads data from the TCP stream being looped back via DERP.
// It reports to `readFinishedC` when `size` bytes have been read, or if an
// error occurs.
wg.Add(1)
readFinishedC := make(chan error, 1)
go func() {
defer wg.Done()
readConn, err := ln.Accept()
if err != nil {
readFinishedC <- err
return
}
defer readConn.Close()
deadline, ok := ctx.Deadline()
if ok {
// Don't try reading past our context's deadline.
if err := readConn.SetReadDeadline(deadline); err != nil {
readFinishedC <- fmt.Errorf("unable to set read deadline: %w", err)
return
}
}
n, err := io.CopyN(io.Discard, readConn, size)
// Measure transfer time and bytes transferred irrespective of whether it succeeded or failed.
transferTimeSeconds.Add(time.Since(start).Seconds())
totalBytesTransferred.Add(float64(n))
readFinishedC <- err
}()
// This goroutine sends data to the TCP stream being looped back via DERP.
// It only reports errors to `sendErrC`.
wg.Add(1)
sendErrC := make(chan error, 1)
go func() {
defer wg.Done()
for wrote := 0; wrote < int(size); wrote += len(randData) {View on GitHub (pinned to 5201273aec)
Solutions
- If the wrapped error is 'use of closed network connection', treat it as a teardown race, not a bandwidth failure — retry the probe.
- Correlate with the other error channels: whichever fired first is the real fault.
- Update the prober; accept-loop races are the kind of issue fixed over time upstream.
Defensive patterns
Strategy: try-catch
Validate before calling
// Avoid setting a deadline on an already-expired context.
if ctx.Err() != nil {
return ctx.Err()
} Type guard
func isConnClosed(err error) bool {
return errors.Is(err, net.ErrClosed)
} Try / catch
Check the wrapped error with errors.Is(err, net.ErrClosed): a closed-connection cause means teardown race — ignore or log at debug; any other cause is worth investigating on the platform in use.
Prevention
- Cancel probe contexts before closing listeners/conns in teardown paths
- Treat 'unable to set read deadline' alongside other channel errors as secondary noise
- Keep the prober updated; accept-loop races get fixed upstream
When it happens
Trigger: readConn.SetReadDeadline(deadline) errors: the accepted connection was closed concurrently (probe teardown racing the accept loop), or the runtime rejects the deadline value on the platform in use.
Common situations: Race between probe cancellation (function returns, deferred closes run) and the accept/read goroutine; usually appears alongside other channel errors from the same teardown and is noise rather than the root cause.
Related errors
- dial of %v: %v
- porttrack: failed to connect to collector at %s: %v
- porttrack: failed to report port to collector: %v
- failed to listen: %s
- failed to dial address %q: %w
AI-assisted analysis of tailscale/tailscale@5201273aec (2026-08-18).
Data as JSON: /api/errors/ebb953a844466f2b.
Report an issue: GitHub.