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

  1. If the wrapped error is 'use of closed network connection', treat it as a teardown race, not a bandwidth failure — retry the probe.
  2. Correlate with the other error channels: whichever fired first is the real fault.
  3. 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

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


AI-assisted analysis of tailscale/tailscale@5201273aec (2026-08-18). Data as JSON: /api/errors/ebb953a844466f2b. Report an issue: GitHub.