}), func(ts *httptest.Server) {
ts.Config.ReadHeaderTimeout = -1 // don't time out while reading headers
ts.Config.ReadTimeout = timeout
+ t.Logf("Server.Config.ReadTimeout = %v", timeout)
})
+
+ var retries atomic.Int32
+ cst.c.Transport.(*Transport).Proxy = func(*Request) (*url.URL, error) {
+ if retries.Add(1) != 1 {
+ return nil, errors.New("too many retries")
+ }
+ return nil, nil
+ }
+
pr, pw := io.Pipe()
res, err := cst.c.Post(cst.ts.URL, "text/apocryphal", pr)
if err != nil {
errc <- err
}), func(ts *httptest.Server) {
ts.Config.WriteTimeout = timeout
+ t.Logf("Server.Config.WriteTimeout = %v", timeout)
})
+
+ // The server's WriteTimeout parameter also applies to reads during the TLS
+ // handshake. The client makes the last write during the handshake, and if
+ // the server happens to time out during the read of that write, the client
+ // may think that the connection was accepted even though the server thinks
+ // it timed out.
+ //
+ // The client only notices that the server connection is gone when it goes
+ // to actually write the request — and when that fails, it retries
+ // internally (the same as if the server had closed the connection due to a
+ // racing idle-timeout).
+ //
+ // With unlucky and very stable scheduling (as may be the case with the fake wasm
+ // net stack), this can result in an infinite retry loop that doesn't
+ // propagate the error up far enough for us to adjust the WriteTimeout.
+ //
+ // To avoid that problem, we explicitly forbid internal retries by rejecting
+ // them in a Proxy hook in the transport.
+ var retries atomic.Int32
+ cst.c.Transport.(*Transport).Proxy = func(*Request) (*url.URL, error) {
+ if retries.Add(1) != 1 {
+ return nil, errors.New("too many retries")
+ }
+ return nil, nil
+ }
+
res, err := cst.c.Get(cst.ts.URL)
if err != nil {
// Probably caused by the write timeout expiring before the handler runs.
}
}), func(ts *httptest.Server) {
ts.Config.ReadTimeout = timeout
+ t.Logf("Server.Config.ReadTimeout = %v", timeout)
})
defer cst.close()
ts := cst.ts
+ var retries atomic.Int32
+ cst.c.Transport.(*Transport).Proxy = func(*Request) (*url.URL, error) {
+ if retries.Add(1) != 1 {
+ return nil, errors.New("too many retries")
+ }
+ return nil, nil
+ }
+
c := ts.Client()
res, err := c.Get(ts.URL)