]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: make timeout errors match context.DeadlineExceeded
authorDamien Neil <dneil@google.com>
Tue, 27 Feb 2024 22:53:33 +0000 (14:53 -0800)
committerDamien Neil <dneil@google.com>
Wed, 28 Feb 2024 17:11:55 +0000 (17:11 +0000)
When returning an error which implements net.Error and reports
itself as a timeout, also report it as matching context.DeadlineExceeded.
This matches the behavior of timeout errors in the net package
and elsewhere.

Fixes #50856

Change-Id: I2ca911e3677a699af27ba89b1200401baa8b3b1b
Reviewed-on: https://go-review.googlesource.com/c/go/+/567537
Reviewed-by: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/net/http/client.go
src/net/http/client_test.go
src/net/http/transport.go

index ee6de24fc19e4fbe8003a3def4fdcc426acd0ae4..99ed7dc927faf609431ce2d23062ce9a28bdd7f8 100644 (file)
@@ -725,10 +725,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) {
                        // c.send() always closes req.Body
                        reqBodyClosed = true
                        if !deadline.IsZero() && didTimeout() {
-                               err = &httpError{
-                                       err:     err.Error() + " (Client.Timeout exceeded while awaiting headers)",
-                                       timeout: true,
-                               }
+                               err = &timeoutError{err.Error() + " (Client.Timeout exceeded while awaiting headers)"}
                        }
                        return nil, uerr(err)
                }
@@ -968,10 +965,7 @@ func (b *cancelTimerBody) Read(p []byte) (n int, err error) {
                return n, err
        }
        if b.reqDidTimeout() {
-               err = &httpError{
-                       err:     err.Error() + " (Client.Timeout or context cancellation while reading body)",
-                       timeout: true,
-               }
+               err = &timeoutError{err.Error() + " (Client.Timeout or context cancellation while reading body)"}
        }
        return n, err
 }
index 7459b9cb6ed1df3a42d4ed54a54abc8748b3dffd..80a6664d3b55880cc623c44529628a1e7e2e4d1a 100644 (file)
@@ -1249,6 +1249,9 @@ func testClientTimeout(t *testing.T, mode testMode) {
                } else if !ne.Timeout() {
                        t.Errorf("net.Error.Timeout = false; want true")
                }
+               if !errors.Is(err, context.DeadlineExceeded) {
+                       t.Errorf("ReadAll error = %q; expected some context.DeadlineExceeded", err)
+               }
                if got := ne.Error(); !strings.Contains(got, "(Client.Timeout") {
                        if runtime.GOOS == "windows" && strings.HasPrefix(runtime.GOARCH, "arm") {
                                testenv.SkipFlaky(t, 43120)
@@ -1292,6 +1295,9 @@ func testClientTimeout_Headers(t *testing.T, mode testMode) {
        if !ne.Timeout() {
                t.Error("net.Error.Timeout = false; want true")
        }
+       if !errors.Is(err, context.DeadlineExceeded) {
+               t.Errorf("ReadAll error = %q; expected some context.DeadlineExceeded", err)
+       }
        if got := ne.Error(); !strings.Contains(got, "Client.Timeout exceeded") {
                if runtime.GOOS == "windows" && strings.HasPrefix(runtime.GOARCH, "arm") {
                        testenv.SkipFlaky(t, 43120)
@@ -1992,6 +1998,9 @@ func testClientDoCanceledVsTimeout(t *testing.T, mode testMode) {
                        if g, w := ue.Err, wantErr; g != w {
                                t.Errorf("url.Error.Err = %v; want %v", g, w)
                        }
+                       if got := errors.Is(err, context.DeadlineExceeded); got != wantIsTimeout {
+                               t.Errorf("errors.Is(err, context.DeadlineExceeded) = %v, want %v", got, wantIsTimeout)
+                       }
                })
        }
 }
index 411f6b2912010099f7b920c9c883e36242b56cd7..75934f00de81f8bf79991431c67bb6f89293a0a1 100644 (file)
@@ -2557,16 +2557,18 @@ type writeRequest struct {
        continueCh <-chan struct{}
 }
 
-type httpError struct {
-       err     string
-       timeout bool
+// httpTimeoutError represents a timeout.
+// It implements net.Error and wraps context.DeadlineExceeded.
+type timeoutError struct {
+       err string
 }
 
-func (e *httpError) Error() string   { return e.err }
-func (e *httpError) Timeout() bool   { return e.timeout }
-func (e *httpError) Temporary() bool { return true }
+func (e *timeoutError) Error() string     { return e.err }
+func (e *timeoutError) Timeout() bool     { return true }
+func (e *timeoutError) Temporary() bool   { return true }
+func (e *timeoutError) Is(err error) bool { return err == context.DeadlineExceeded }
 
-var errTimeout error = &httpError{err: "net/http: timeout awaiting response headers", timeout: true}
+var errTimeout error = &timeoutError{"net/http: timeout awaiting response headers"}
 
 // errRequestCanceled is set to be identical to the one from h2 to facilitate
 // testing.