Go version
go version go1.23.2 darwin/arm64
Output of go env
in your module/workspace:
GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/zy.chen/Library/Caches/go-build'
GOENV='/Users/zy.chen/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/zy.chen/go/pkg/mod'
GONOPROXY='github.com/AfterShip'
GONOSUMDB='github.com/AfterShip'
GOOS='darwin'
GOPATH='/Users/zy.chen/go'
GOPRIVATE='github.com/AfterShip'
GOPROXY='direct'
GOROOT='/opt/homebrew/Cellar/go/1.23.2/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.23.2/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.23.2'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/zy.chen/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/opt/homebrew/opt/go/libexec/src/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/fm/spjh4bvs1hzfsg9w8ybmc1zm0000gn/T/go-build2972010941=/tmp/go-build -gno-record-gcc-switches -fno-common'
What did you do?
Found an inconsistency in error message in net/http/client.go
.
What did you see happen?
The current error message says:
if b.reqDidTimeout() {
err = &timeoutError{err.Error() + " (Client.Timeout or context cancellation while reading body)"}
}
However, when reqDidTimeout() returns true in cancelTimerBody.Read(), it should only be due to Client.Timeout expiration, so the error message should only mention "Client.Timeout".
What did you expect to see?
The error message should only mention "Client.Timeout" as below:
err = &timeoutError{err.Error() + " (Client.Timeout exceeded while reading body)"}
Analysis
After analyzing the code in setRequestCancel()
, reqDidTimeout()
only returns true when:
- fast path: return true when client timeout exceeds
if req.Cancel == nil && knownTransport {
// If they already had a Request.Context that's
// expiring sooner, do nothing:
if !timeBeforeContextDeadline(deadline, oldCtx) {
return nop, alwaysFalse
}
var cancelCtx func()
req.ctx, cancelCtx = context.WithDeadline(oldCtx, deadline)
return cancelCtx, func() bool { return time.Now().After(deadline) }
}
- normal path: return true when the timer created with deadline fires
(<-timer.C)
timer := time.NewTimer(time.Until(deadline))
var timedOut atomic.Bool
go func() {
select {
case <-initialReqCancel:
doCancel()
timer.Stop()
case <-timer.C:
timedOut.Store(true)
doCancel()
case <-stopTimerCh:
timer.Stop()
}
}()
return stopTimer, timedOut.Load
Context cancellation or timeout does not trigger timedOut.Store(true)
, so when reqDidTimeout()
returns true, it's always due to client timeout.
Comment From: seankhliao
or it may be a bug that a body read isn't cancelled when the request context is
Comment From: gabyhelp
Related Issues
- net/http: Client returns "request canceled" on timeout #17711 (closed)
- net/http: canceled requests with Timeout set always return a Timeout error #16094 (closed)
- net/http: request context cancelled on readtimeout, persists across connection reuse #70834
- net/http: 1.17.3 setting http.Client.Timeout returns context cancelled on Body.Close #49517 (closed)
- net/http: Client does not wrap context errors #50856 (closed)
- net/http: context deadline can cause surprising errors #64449
- net/http: setting Timeout in http.Client produces unexpected "context canceled" on body.Close #49521 (closed)
- net/http: Client.Do() in HTTP/2 mode returns unexpected nil error after client timeout #65375 (closed)
- net/http: Dial I/O Timeout even when request context is not canceled #36848
- net/http: errors from Client.Timeout triggering have .Timeout()==false #9405 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: cherrymui
cc @neild
Comment From: neild
Looks like this error used to be returned on a path where either Client.Timeout or context cancellation could occur, but some refactoring has left it only happening after Timeout.