{"record":{"id":"ebb953a844466f2b","repo":"tailscale/tailscale","slug":"unable-to-set-read-deadline-w","errorCode":null,"errorMessage":"unable to set read deadline: %w","messagePattern":"unable to set read deadline: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"prober/derp.go","lineNumber":1090,"sourceCode":"\t// This goroutine reads data from the TCP stream being looped back via DERP.\n\t// It reports to `readFinishedC` when `size` bytes have been read, or if an\n\t// error occurs.\n\twg.Add(1)\n\treadFinishedC := make(chan error, 1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\treadConn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\treadFinishedC <- err\n\t\t\treturn\n\t\t}\n\t\tdefer readConn.Close()\n\t\tdeadline, ok := ctx.Deadline()\n\t\tif ok {\n\t\t\t// Don't try reading past our context's deadline.\n\t\t\tif err := readConn.SetReadDeadline(deadline); err != nil {\n\t\t\t\treadFinishedC <- fmt.Errorf(\"unable to set read deadline: %w\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tn, err := io.CopyN(io.Discard, readConn, size)\n\t\t// Measure transfer time and bytes transferred irrespective of whether it succeeded or failed.\n\t\ttransferTimeSeconds.Add(time.Since(start).Seconds())\n\t\ttotalBytesTransferred.Add(float64(n))\n\t\treadFinishedC <- err\n\t}()\n\n\t// This goroutine sends data to the TCP stream being looped back via DERP.\n\t// It only reports errors to `sendErrC`.\n\twg.Add(1)\n\tsendErrC := make(chan error, 1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tfor wrote := 0; wrote < int(size); wrote += len(randData) {","sourceCodeStart":1072,"sourceCodeEnd":1108,"githubUrl":"https://github.com/tailscale/tailscale/blob/5201273aec737d6372ab7423c31c04ca3ca2a0c2/prober/derp.go#L1072-L1108","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Avoid setting a deadline on an already-expired context.\nif ctx.Err() != nil {\n    return ctx.Err()\n}","typeGuard":"func isConnClosed(err error) bool {\n    return errors.Is(err, net.ErrClosed)\n}","tryCatchPattern":"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.","preventionTips":["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"],"tags":["tcp","read-deadline","race","go"],"backgroundTag":"set-deadline-failed","analyzedSha":"5201273aec737d6372ab7423c31c04ca3ca2a0c2","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}