diff --git a/doc/go1.15.html b/doc/go1.15.html index a4f78c1c78a991fc1943b431dfb9733c6faed763..9d10092ffa7be4d99ffd4bb803995d5c1debde3f 100644 --- a/doc/go1.15.html +++ b/doc/go1.15.html @@ -172,6 +172,25 @@ TODO </dd> </dl> +<dl id="net"><dt><a href="/pkg/net/">net</a></dt> + <dd> + <p><!-- CL --> + If an I/O operation exceeds a deadline set by + the <a href="/pkg/net/#Conn"><code>Conn.SetDeadline</code></a>, + <code>Conn.SetReadDeadline</code>, + or <code>Conn.SetWriteDeadline</code> methods, it will now + return an error that is or wraps + <a href="/pkg/os#ErrDeadlineExceeded"><code>os.ErrDeadlineExceeded</code></a>. + This may be used to reliably detect whether an error is due to + an exceeded deadline. + Earlier releases recommended calling the <code>Timeout</code> + method on the error, but I/O operations can return errors for + which <code>Timeout</code> returns <code>true</code> although a + deadline has not been exceeded. + </p> + </dd> +</dl> + <dl id="net/http/pprof"><dt><a href="/pkg/net/http/pprof/">net/http/pprof</a></dt> <dd> <p><!-- CL 147598, 229537 --> @@ -200,6 +219,25 @@ TODO </dd> </dl> +<dl id="os"><dt><a href="/pkg/os/">os</a></dt> + <dd> + <p><!-- CL --> + If an I/O operation exceeds a deadline set by + the <a href="/pkg/os/#File.SetDeadline"><code>File.SetDeadline</code></a>, + <a href="/pkg/os/#File.SetReadDeadline"><code>File.SetReadDeadline</code></a>, + or <a href="/pkg/os/#File.SetWriteDeadline"><code>File.SetWriteDeadline</code></a> + methods, it will now return an error that is or wraps + <a href="/pkg/os#ErrDeadlineExceeded"><code>os.ErrDeadlineExceeded</code></a>. + This may be used to reliably detect whether an error is due to + an exceeded deadline. + Earlier releases recommended calling the <code>Timeout</code> + method on the error, but I/O operations can return errors for + which <code>Timeout</code> returns <code>true</code> although a + deadline has not been exceeded. + </p> + </dd> +</dl> + <dl id="reflect"><dt><a href="/pkg/reflect/">reflect</a></dt> <dd> <p><!-- CL 228902 --> diff --git a/src/internal/poll/fd.go b/src/internal/poll/fd.go index c0de50c1b46e55ba6c658dfa352ac1a2b67d17fb..b72ea3d55c2450ac216a7e599e1dc59ee741aec9 100644 --- a/src/internal/poll/fd.go +++ b/src/internal/poll/fd.go @@ -35,16 +35,20 @@ func errClosing(isFile bool) error { return ErrNetClosing } -// ErrTimeout is returned for an expired deadline. -var ErrTimeout error = &TimeoutError{} +// ErrDeadlineExceeded is returned for an expired deadline. +// This is exported by the os package as os.ErrDeadlineExceeded. +var ErrDeadlineExceeded error = &DeadlineExceededError{} -// TimeoutError is returned for an expired deadline. -type TimeoutError struct{} +// DeadlineExceededError is returned for an expired deadline. +type DeadlineExceededError struct{} // Implement the net.Error interface. -func (e *TimeoutError) Error() string { return "i/o timeout" } -func (e *TimeoutError) Timeout() bool { return true } -func (e *TimeoutError) Temporary() bool { return true } +// The string is "i/o timeout" because that is what was returned +// by earlier Go versions. Changing it may break programs that +// match on error strings. +func (e *DeadlineExceededError) Error() string { return "i/o timeout" } +func (e *DeadlineExceededError) Timeout() bool { return true } +func (e *DeadlineExceededError) Temporary() bool { return true } // ErrNotPollable is returned when the file or socket is not suitable // for event notification. diff --git a/src/internal/poll/fd_plan9.go b/src/internal/poll/fd_plan9.go index 0fce32915e7dd41eda9b6627a295aa52ac6493f7..e57e0419c5532da591ddc9d6e3347005f7c3bd5b 100644 --- a/src/internal/poll/fd_plan9.go +++ b/src/internal/poll/fd_plan9.go @@ -60,7 +60,7 @@ func (fd *FD) Close() error { // Read implements io.Reader. func (fd *FD) Read(fn func([]byte) (int, error), b []byte) (int, error) { if fd.rtimedout.isSet() { - return 0, ErrTimeout + return 0, ErrDeadlineExceeded } if err := fd.readLock(); err != nil { return 0, err @@ -76,7 +76,7 @@ func (fd *FD) Read(fn func([]byte) (int, error), b []byte) (int, error) { err = io.EOF } if isInterrupted(err) { - err = ErrTimeout + err = ErrDeadlineExceeded } return n, err } @@ -84,7 +84,7 @@ func (fd *FD) Read(fn func([]byte) (int, error), b []byte) (int, error) { // Write implements io.Writer. func (fd *FD) Write(fn func([]byte) (int, error), b []byte) (int, error) { if fd.wtimedout.isSet() { - return 0, ErrTimeout + return 0, ErrDeadlineExceeded } if err := fd.writeLock(); err != nil { return 0, err @@ -94,7 +94,7 @@ func (fd *FD) Write(fn func([]byte) (int, error), b []byte) (int, error) { n, err := fd.waio.Wait() fd.waio = nil if isInterrupted(err) { - err = ErrTimeout + err = ErrDeadlineExceeded } return n, err } diff --git a/src/internal/poll/fd_poll_js.go b/src/internal/poll/fd_poll_js.go index 2bfeb0a0b7af992f2f38cd0026800a6caf2b2056..d6b28e503cda489362f9c92c2430d3b538095170 100644 --- a/src/internal/poll/fd_poll_js.go +++ b/src/internal/poll/fd_poll_js.go @@ -45,7 +45,7 @@ func (pd *pollDesc) wait(mode int, isFile bool) error { if isFile { // TODO(neelance): wasm: Use callbacks from JS to block until the read/write finished. return nil } - return ErrTimeout + return ErrDeadlineExceeded } func (pd *pollDesc) waitRead(isFile bool) error { return pd.wait('r', isFile) } diff --git a/src/internal/poll/fd_poll_runtime.go b/src/internal/poll/fd_poll_runtime.go index fd73166ac3ec2329a21de2dfd49f520ac9e6cc51..222e5c6707afd08a4a9b96bdb0463631d4b205d9 100644 --- a/src/internal/poll/fd_poll_runtime.go +++ b/src/internal/poll/fd_poll_runtime.go @@ -123,7 +123,7 @@ func convertErr(res int, isFile bool) error { case pollErrClosing: return errClosing(isFile) case pollErrTimeout: - return ErrTimeout + return ErrDeadlineExceeded case pollErrNotPollable: return ErrNotPollable } diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go index 1a0bdb34fec571b371dbdcdc8626feb3c29267cf..e1ef6199b37a44a1d9ba581a15d60463bcba4a2d 100644 --- a/src/internal/poll/fd_windows.go +++ b/src/internal/poll/fd_windows.go @@ -188,7 +188,7 @@ func execIO(o *operation, submit func(o *operation) error) (int, error) { // IO is interrupted by "close" or "timeout" netpollErr := err switch netpollErr { - case ErrNetClosing, ErrFileClosing, ErrTimeout: + case ErrNetClosing, ErrFileClosing, ErrDeadlineExceeded: // will deal with those. default: panic("unexpected runtime.netpoll error: " + netpollErr.Error()) diff --git a/src/net/dial.go b/src/net/dial.go index d8be1c222d042f70b03bfe726a70f644da2c5d8a..13a312a91ae06b41a6a119d064a55b0a57bc2437 100644 --- a/src/net/dial.go +++ b/src/net/dial.go @@ -7,7 +7,6 @@ package net import ( "context" "internal/nettrace" - "internal/poll" "syscall" "time" ) @@ -141,7 +140,7 @@ func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, er } timeRemaining := deadline.Sub(now) if timeRemaining <= 0 { - return time.Time{}, poll.ErrTimeout + return time.Time{}, errTimeout } // Tentatively allocate equal time to each remaining address. timeout := timeRemaining / time.Duration(addrsRemaining) diff --git a/src/net/dial_test.go b/src/net/dial_test.go index aedf643e98d2255f32079afa5afc036106de8dae..01582489de12af4e64eb02c37b56c5c4a37411d1 100644 --- a/src/net/dial_test.go +++ b/src/net/dial_test.go @@ -9,7 +9,6 @@ package net import ( "bufio" "context" - "internal/poll" "internal/testenv" "io" "os" @@ -540,8 +539,8 @@ func TestDialerPartialDeadline(t *testing.T) { {now, noDeadline, 1, noDeadline, nil}, // Step the clock forward and cross the deadline. {now.Add(-1 * time.Millisecond), now, 1, now, nil}, - {now.Add(0 * time.Millisecond), now, 1, noDeadline, poll.ErrTimeout}, - {now.Add(1 * time.Millisecond), now, 1, noDeadline, poll.ErrTimeout}, + {now.Add(0 * time.Millisecond), now, 1, noDeadline, errTimeout}, + {now.Add(1 * time.Millisecond), now, 1, noDeadline, errTimeout}, } for i, tt := range testCases { deadline, err := partialDeadline(tt.now, tt.deadline, tt.addrs) diff --git a/src/net/dnsclient_unix_test.go b/src/net/dnsclient_unix_test.go index 2ad40dfe02b92b6a41732eb01ca5374dd52196fd..06553636eee253306d74cd943a11e782347183fc 100644 --- a/src/net/dnsclient_unix_test.go +++ b/src/net/dnsclient_unix_test.go @@ -10,7 +10,6 @@ import ( "context" "errors" "fmt" - "internal/poll" "io/ioutil" "os" "path" @@ -480,7 +479,7 @@ func TestGoLookupIPWithResolverConfig(t *testing.T) { break default: time.Sleep(10 * time.Millisecond) - return dnsmessage.Message{}, poll.ErrTimeout + return dnsmessage.Message{}, os.ErrDeadlineExceeded } r := dnsmessage.Message{ Header: dnsmessage.Header{ @@ -993,7 +992,7 @@ func TestRetryTimeout(t *testing.T) { if s == "192.0.2.1:53" { deadline0 = deadline time.Sleep(10 * time.Millisecond) - return dnsmessage.Message{}, poll.ErrTimeout + return dnsmessage.Message{}, os.ErrDeadlineExceeded } if deadline.Equal(deadline0) { @@ -1131,7 +1130,7 @@ func TestStrictErrorsLookupIP(t *testing.T) { } makeTimeout := func() error { return &DNSError{ - Err: poll.ErrTimeout.Error(), + Err: os.ErrDeadlineExceeded.Error(), Name: name, Server: server, IsTimeout: true, @@ -1247,7 +1246,7 @@ func TestStrictErrorsLookupIP(t *testing.T) { Questions: q.Questions, }, nil case resolveTimeout: - return dnsmessage.Message{}, poll.ErrTimeout + return dnsmessage.Message{}, os.ErrDeadlineExceeded default: t.Fatal("Impossible resolveWhich") } @@ -1372,7 +1371,7 @@ func TestStrictErrorsLookupTXT(t *testing.T) { switch q.Questions[0].Name.String() { case searchX: - return dnsmessage.Message{}, poll.ErrTimeout + return dnsmessage.Message{}, os.ErrDeadlineExceeded case searchY: return mockTXTResponse(q), nil default: @@ -1387,7 +1386,7 @@ func TestStrictErrorsLookupTXT(t *testing.T) { var wantRRs int if strict { wantErr = &DNSError{ - Err: poll.ErrTimeout.Error(), + Err: os.ErrDeadlineExceeded.Error(), Name: name, Server: server, IsTimeout: true, @@ -1415,7 +1414,7 @@ func TestDNSGoroutineRace(t *testing.T) { fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) { time.Sleep(10 * time.Microsecond) - return dnsmessage.Message{}, poll.ErrTimeout + return dnsmessage.Message{}, os.ErrDeadlineExceeded }} r := Resolver{PreferGo: true, Dial: fake.DialContext} diff --git a/src/net/error_test.go b/src/net/error_test.go index 89dcc2e6e6e1497b4b04413a0b2e07dcf5f49057..8d4a7ffb3d033b90f4b032811181b47d7ccf83c1 100644 --- a/src/net/error_test.go +++ b/src/net/error_test.go @@ -91,7 +91,7 @@ second: return nil } switch err := nestedErr.(type) { - case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *poll.TimeoutError, UnknownNetworkError: + case *AddrError, addrinfoErrno, *timeoutError, *DNSError, InvalidAddrError, *ParseError, *poll.DeadlineExceededError, UnknownNetworkError: return nil case *os.SyscallError: nestedErr = err.Err @@ -436,7 +436,7 @@ second: goto third } switch nestedErr { - case poll.ErrNetClosing, poll.ErrTimeout, poll.ErrNotPollable: + case poll.ErrNetClosing, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded: return nil } return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) @@ -471,14 +471,14 @@ second: return nil } switch err := nestedErr.(type) { - case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *poll.TimeoutError, UnknownNetworkError: + case *AddrError, addrinfoErrno, *timeoutError, *DNSError, InvalidAddrError, *ParseError, *poll.DeadlineExceededError, UnknownNetworkError: return nil case *os.SyscallError: nestedErr = err.Err goto third } switch nestedErr { - case errCanceled, poll.ErrNetClosing, errMissingAddress, poll.ErrTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF: + case errCanceled, poll.ErrNetClosing, errMissingAddress, errTimeout, os.ErrDeadlineExceeded, ErrWriteToConnected, io.ErrUnexpectedEOF: return nil } return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) @@ -627,7 +627,7 @@ second: goto third } switch nestedErr { - case poll.ErrNetClosing, poll.ErrTimeout, poll.ErrNotPollable: + case poll.ErrNetClosing, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded: return nil } return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr) diff --git a/src/net/net.go b/src/net/net.go index 1d7e5e7f65253198a2400064cc76ac0597316236..82b71565aa6f0df16810bcfc9a78b543ce87bcf4 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -81,7 +81,6 @@ package net import ( "context" "errors" - "internal/poll" "io" "os" "sync" @@ -136,23 +135,22 @@ type Conn interface { // SetReadDeadline and SetWriteDeadline. // // A deadline is an absolute time after which I/O operations - // fail with a timeout (see type Error) instead of - // blocking. The deadline applies to all future and pending - // I/O, not just the immediately following call to Read or - // Write. After a deadline has been exceeded, the connection - // can be refreshed by setting a deadline in the future. + // fail instead of blocking. The deadline applies to all future + // and pending I/O, not just the immediately following call to + // Read or Write. After a deadline has been exceeded, the + // connection can be refreshed by setting a deadline in the future. + // + // If the deadline is exceeded a call to Read or Write or to other + // I/O methods will return an error that wraps os.ErrDeadlineExceeded. + // This can be tested using errors.Is(err, os.ErrDeadlineExceeded). + // The error's Timeout method will return true, but note that there + // are other possible errors for which the Timeout method will + // return true even if the deadline has not been exceeded. // // An idle timeout can be implemented by repeatedly extending // the deadline after successful Read or Write calls. // // A zero value for t means I/O operations will not time out. - // - // Note that if a TCP connection has keep-alive turned on, - // which is the default unless overridden by Dialer.KeepAlive - // or ListenConfig.KeepAlive, then a keep-alive failure may - // also return a timeout error. On Unix systems a keep-alive - // failure on I/O can be detected using - // errors.Is(err, syscall.ETIMEDOUT). SetDeadline(t time.Time) error // SetReadDeadline sets the deadline for future Read calls @@ -420,7 +418,7 @@ func mapErr(err error) error { case context.Canceled: return errCanceled case context.DeadlineExceeded: - return poll.ErrTimeout + return errTimeout default: return err } @@ -567,6 +565,21 @@ func (e InvalidAddrError) Error() string { return string(e) } func (e InvalidAddrError) Timeout() bool { return false } func (e InvalidAddrError) Temporary() bool { return false } +// errTimeout exists to return the historical "i/o timeout" string +// for context.DeadlineExceeded. See mapErr. +// It is also used when Dialer.Deadline is exceeded. +// +// TODO(iant): We could consider changing this to os.ErrDeadlineExceeded +// in the future, but note that that would conflict with the TODO +// at mapErr that suggests changing it to context.DeadlineExceeded. +var errTimeout error = &timeoutError{} + +type timeoutError struct{} + +func (e *timeoutError) Error() string { return "i/o timeout" } +func (e *timeoutError) Timeout() bool { return true } +func (e *timeoutError) Temporary() bool { return true } + // DNSConfigError represents an error reading the machine's DNS configuration. // (No longer used; kept for compatibility.) type DNSConfigError struct { diff --git a/src/net/pipe.go b/src/net/pipe.go index 9177fc403643e3b9de936811a9c1873efa5a34cf..f1741938b0bb8fc525dfd5cdeba9b793e481673b 100644 --- a/src/net/pipe.go +++ b/src/net/pipe.go @@ -6,6 +6,7 @@ package net import ( "io" + "os" "sync" "time" ) @@ -78,12 +79,6 @@ func isClosedChan(c <-chan struct{}) bool { } } -type timeoutError struct{} - -func (timeoutError) Error() string { return "deadline exceeded" } -func (timeoutError) Timeout() bool { return true } -func (timeoutError) Temporary() bool { return true } - type pipeAddr struct{} func (pipeAddr) Network() string { return "pipe" } @@ -158,7 +153,7 @@ func (p *pipe) read(b []byte) (n int, err error) { case isClosedChan(p.remoteDone): return 0, io.EOF case isClosedChan(p.readDeadline.wait()): - return 0, timeoutError{} + return 0, os.ErrDeadlineExceeded } select { @@ -171,7 +166,7 @@ func (p *pipe) read(b []byte) (n int, err error) { case <-p.remoteDone: return 0, io.EOF case <-p.readDeadline.wait(): - return 0, timeoutError{} + return 0, os.ErrDeadlineExceeded } } @@ -190,7 +185,7 @@ func (p *pipe) write(b []byte) (n int, err error) { case isClosedChan(p.remoteDone): return 0, io.ErrClosedPipe case isClosedChan(p.writeDeadline.wait()): - return 0, timeoutError{} + return 0, os.ErrDeadlineExceeded } p.wrMu.Lock() // Ensure entirety of b is written together @@ -206,7 +201,7 @@ func (p *pipe) write(b []byte) (n int, err error) { case <-p.remoteDone: return n, io.ErrClosedPipe case <-p.writeDeadline.wait(): - return n, timeoutError{} + return n, os.ErrDeadlineExceeded } } return n, nil diff --git a/src/net/rawconn_test.go b/src/net/rawconn_test.go index 9a82f8f78e02b02cd3a2c3264a5bf688d45afbb8..a08ff89d1af8d9690c376e34ba3bd4bcac498670 100644 --- a/src/net/rawconn_test.go +++ b/src/net/rawconn_test.go @@ -130,7 +130,7 @@ func TestRawConnReadWrite(t *testing.T) { if perr := parseWriteError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Errorf("got %v; want timeout", err) } if _, err = readRawConn(cc, b[:]); err == nil { @@ -139,7 +139,7 @@ func TestRawConnReadWrite(t *testing.T) { if perr := parseReadError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Errorf("got %v; want timeout", err) } @@ -153,7 +153,7 @@ func TestRawConnReadWrite(t *testing.T) { if perr := parseReadError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Errorf("got %v; want timeout", err) } @@ -167,7 +167,7 @@ func TestRawConnReadWrite(t *testing.T) { if perr := parseWriteError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Errorf("got %v; want timeout", err) } }) diff --git a/src/net/timeout_test.go b/src/net/timeout_test.go index 51123dfbc46b44142029aebe4292de18323203b9..ad14cd79ac43caa12ef3523334c970dcfcc863c3 100644 --- a/src/net/timeout_test.go +++ b/src/net/timeout_test.go @@ -7,12 +7,13 @@ package net import ( + "errors" "fmt" - "internal/poll" "internal/testenv" "io" "io/ioutil" "net/internal/socktest" + "os" "runtime" "sync" "testing" @@ -148,9 +149,9 @@ var acceptTimeoutTests = []struct { }{ // Tests that accept deadlines in the past work, even if // there's incoming connections available. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {50 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestAcceptTimeout(t *testing.T) { @@ -194,7 +195,7 @@ func TestAcceptTimeout(t *testing.T) { if perr := parseAcceptError(err); perr != nil { t.Errorf("#%d/%d: %v", i, j, perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatalf("#%d/%d: %v", i, j, err) } } @@ -250,7 +251,7 @@ func TestAcceptTimeoutMustReturn(t *testing.T) { if perr := parseAcceptError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -302,9 +303,9 @@ var readTimeoutTests = []struct { }{ // Tests that read deadlines work, even if there's data ready // to be read. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {50 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestReadTimeout(t *testing.T) { @@ -344,7 +345,7 @@ func TestReadTimeout(t *testing.T) { if perr := parseReadError(err); perr != nil { t.Errorf("#%d/%d: %v", i, j, perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatalf("#%d/%d: %v", i, j, err) } } @@ -423,9 +424,9 @@ var readFromTimeoutTests = []struct { }{ // Tests that read deadlines work, even if there's data ready // to be read. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {50 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestReadFromTimeout(t *testing.T) { @@ -468,7 +469,7 @@ func TestReadFromTimeout(t *testing.T) { if perr := parseReadError(err); perr != nil { t.Errorf("#%d/%d: %v", i, j, perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatalf("#%d/%d: %v", i, j, err) } } @@ -491,9 +492,9 @@ var writeTimeoutTests = []struct { }{ // Tests that write deadlines work, even if there's buffer // space available to write. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {10 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestWriteTimeout(t *testing.T) { @@ -522,7 +523,7 @@ func TestWriteTimeout(t *testing.T) { if perr := parseWriteError(err); perr != nil { t.Errorf("#%d/%d: %v", i, j, perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatalf("#%d/%d: %v", i, j, err) } } @@ -605,9 +606,9 @@ var writeToTimeoutTests = []struct { }{ // Tests that write deadlines work, even if there's buffer // space available to write. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {10 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestWriteToTimeout(t *testing.T) { @@ -641,7 +642,7 @@ func TestWriteToTimeout(t *testing.T) { if perr := parseWriteError(err); perr != nil { t.Errorf("#%d/%d: %v", i, j, perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatalf("#%d/%d: %v", i, j, err) } } @@ -685,7 +686,7 @@ func TestReadTimeoutFluctuation(t *testing.T) { if perr := parseReadError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -718,7 +719,7 @@ func TestReadFromTimeoutFluctuation(t *testing.T) { if perr := parseReadError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -760,7 +761,7 @@ func TestWriteTimeoutFluctuation(t *testing.T) { if perr := parseWriteError(err); perr != nil { t.Error(perr) } - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -1073,3 +1074,20 @@ func TestConcurrentSetDeadline(t *testing.T) { } wg.Wait() } + +// isDeadlineExceeded reports whether err is or wraps os.ErrDeadlineExceeded. +// We also check that the error implements net.Error, and that the +// Timeout method returns true. +func isDeadlineExceeded(err error) bool { + nerr, ok := err.(Error) + if !ok { + return false + } + if !nerr.Timeout() { + return false + } + if !errors.Is(err, os.ErrDeadlineExceeded) { + return false + } + return true +} diff --git a/src/net/unixsock_test.go b/src/net/unixsock_test.go index 80cccf21e3d0fd5cac61388fdc8ece70c742b244..4b2cfc4d6283cf091697fe3af223c4e2daf70c55 100644 --- a/src/net/unixsock_test.go +++ b/src/net/unixsock_test.go @@ -113,7 +113,7 @@ func TestUnixgramZeroBytePayload(t *testing.T) { t.Fatalf("unexpected peer address: %v", peer) } default: // Read may timeout, it depends on the platform - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -163,7 +163,7 @@ func TestUnixgramZeroByteBuffer(t *testing.T) { t.Fatalf("unexpected peer address: %v", peer) } default: // Read may timeout, it depends on the platform - if nerr, ok := err.(Error); !ok || !nerr.Timeout() { + if !isDeadlineExceeded(err) { t.Fatal(err) } } diff --git a/src/os/error.go b/src/os/error.go index 26bfe4cab5318487c66571950eb80c90e748b382..875cc9711f8f1e7187e2a98bf3d56b4738adc241 100644 --- a/src/os/error.go +++ b/src/os/error.go @@ -18,11 +18,12 @@ var ( // Methods on File will return this error when the receiver is nil. ErrInvalid = errInvalid() // "invalid argument" - ErrPermission = errPermission() // "permission denied" - ErrExist = errExist() // "file already exists" - ErrNotExist = errNotExist() // "file does not exist" - ErrClosed = errClosed() // "file already closed" - ErrNoDeadline = errNoDeadline() // "file type does not support deadline" + ErrPermission = errPermission() // "permission denied" + ErrExist = errExist() // "file already exists" + ErrNotExist = errNotExist() // "file does not exist" + ErrClosed = errClosed() // "file already closed" + ErrNoDeadline = errNoDeadline() // "file type does not support deadline" + ErrDeadlineExceeded = errDeadlineExceeded() // "i/o timeout" ) func errInvalid() error { return oserror.ErrInvalid } @@ -32,6 +33,15 @@ func errNotExist() error { return oserror.ErrNotExist } func errClosed() error { return oserror.ErrClosed } func errNoDeadline() error { return poll.ErrNoDeadline } +// errDeadlineExceeded returns the value for os.ErrDeadlineExceeded. +// This error comes from the internal/poll package, which is also +// used by package net. Doing this this way ensures that the net +// package will return os.ErrDeadlineExceeded for an exceeded deadline, +// as documented by net.Conn.SetDeadline, without requiring any extra +// work in the net package and without requiring the internal/poll +// package to import os (which it can't, because that would be circular). +func errDeadlineExceeded() error { return poll.ErrDeadlineExceeded } + type timeout interface { Timeout() bool } diff --git a/src/os/file.go b/src/os/file.go index 94341f90e2140a11b13887a01ca278ecf8577f32..57663005a122f2aa7eeb2e8a9ddcdca346a699f4 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -526,10 +526,12 @@ func (f *File) Chmod(mode FileMode) error { return f.chmod(mode) } // After a deadline has been exceeded, the connection can be refreshed // by setting a deadline in the future. // -// An error returned after a timeout fails will implement the -// Timeout method, and calling the Timeout method will return true. -// The PathError and SyscallError types implement the Timeout method. -// In general, call IsTimeout to test whether an error indicates a timeout. +// If the deadline is exceeded a call to Read or Write or to other I/O +// methods will return an error that wraps ErrDeadlineExceeded. +// This can be tested using errors.Is(err, os.ErrDeadlineExceeded). +// That error implements the Timeout method, and calling the Timeout +// method will return true, but there are other possible errors for which +// the Timeout will return true even if the deadline has not been exceeded. // // An idle timeout can be implemented by repeatedly extending // the deadline after successful Read or Write calls. diff --git a/src/os/os_test.go b/src/os/os_test.go index 978e99110c08c9a9eb8db98d6a0f2764ad3cd6a7..f86428b7b9233ea5336a3d76f08057c05a881e36 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -2527,3 +2527,15 @@ func TestReaddirSmallSeek(t *testing.T) { t.Fatalf("first names: %v, second names: %v", names1, names2) } } + +// isDeadlineExceeded reports whether err is or wraps os.ErrDeadlineExceeded. +// We also check that the error has a Timeout method that returns true. +func isDeadlineExceeded(err error) bool { + if !IsTimeout(err) { + return false + } + if !errors.Is(err, ErrDeadlineExceeded) { + return false + } + return true +} diff --git a/src/os/os_unix_test.go b/src/os/os_unix_test.go index 45cb6fc21f2ee7f045786a072142bb384008ea8e..0bce2989c4a4130040141d8fbda2265fa0540349 100644 --- a/src/os/os_unix_test.go +++ b/src/os/os_unix_test.go @@ -275,7 +275,7 @@ func newFileTest(t *testing.T, blocking bool) { _, err := file.Read(b) if !blocking { // We want it to fail with a timeout. - if !IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Fatalf("No timeout reading from file: %v", err) } } else { diff --git a/src/os/timeout_test.go b/src/os/timeout_test.go index 0fe03fa517f43a161b60b63f6dff99c04b2df73b..99b94c2e4cb0bfd1b21436472acd66028804e8a2 100644 --- a/src/os/timeout_test.go +++ b/src/os/timeout_test.go @@ -10,7 +10,6 @@ package os_test import ( "fmt" - "internal/poll" "io" "io/ioutil" "math/rand" @@ -57,9 +56,9 @@ var readTimeoutTests = []struct { }{ // Tests that read deadlines work, even if there's data ready // to be read. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {50 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestReadTimeout(t *testing.T) { @@ -85,7 +84,7 @@ func TestReadTimeout(t *testing.T) { for { n, err := r.Read(b[:]) if xerr != nil { - if !os.IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Fatalf("#%d/%d: %v", i, j, err) } } @@ -148,9 +147,9 @@ var writeTimeoutTests = []struct { }{ // Tests that write deadlines work, even if there's buffer // space available to write. - {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}}, - {10 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, + {10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}}, } func TestWriteTimeout(t *testing.T) { @@ -172,7 +171,7 @@ func TestWriteTimeout(t *testing.T) { for { n, err := w.Write([]byte("WRITE TIMEOUT TEST")) if xerr != nil { - if !os.IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Fatalf("%d: %v", j, err) } } @@ -246,7 +245,7 @@ func timeoutReader(r *os.File, d, min, max time.Duration, ch chan<- error) { var n int n, err = r.Read(b) t1 := time.Now() - if n != 0 || err == nil || !os.IsTimeout(err) { + if n != 0 || err == nil || !isDeadlineExceeded(err) { err = fmt.Errorf("Read did not return (0, timeout): (%d, %v)", n, err) return } @@ -275,7 +274,7 @@ func TestReadTimeoutFluctuation(t *testing.T) { case <-max.C: t.Fatal("Read took over 1s; expected 0.1s") case err := <-ch: - if !os.IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -297,7 +296,7 @@ func timeoutWriter(w *os.File, d, min, max time.Duration, ch chan<- error) { } } t1 := time.Now() - if err == nil || !os.IsTimeout(err) { + if err == nil || !isDeadlineExceeded(err) { err = fmt.Errorf("Write did not return (any, timeout): (%d, %v)", n, err) return } @@ -327,7 +326,7 @@ func TestWriteTimeoutFluctuation(t *testing.T) { case <-max.C: t.Fatalf("Write took over %v; expected 0.1s", d) case err := <-ch: - if !os.IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Fatal(err) } } @@ -438,7 +437,7 @@ func testVariousDeadlines(t *testing.T) { select { case res := <-actvch: - if os.IsTimeout(res.err) { + if !isDeadlineExceeded(err) { t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n) } else { t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err) @@ -494,7 +493,7 @@ func TestReadWriteDeadlineRace(t *testing.T) { var b [1]byte for i := 0; i < N; i++ { _, err := r.Read(b[:]) - if err != nil && !os.IsTimeout(err) { + if err != nil && !isDeadlineExceeded(err) { t.Error("Read returned non-timeout error", err) } } @@ -504,7 +503,7 @@ func TestReadWriteDeadlineRace(t *testing.T) { var b [1]byte for i := 0; i < N; i++ { _, err := w.Write(b[:]) - if err != nil && !os.IsTimeout(err) { + if err != nil && !isDeadlineExceeded(err) { t.Error("Write returned non-timeout error", err) } } @@ -541,7 +540,7 @@ func TestRacyRead(t *testing.T) { _, err := r.Read(b1) copy(b1, b2) // Mutate b1 to trigger potential race if err != nil { - if !os.IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Error(err) } r.SetReadDeadline(time.Now().Add(time.Millisecond)) @@ -580,7 +579,7 @@ func TestRacyWrite(t *testing.T) { _, err := w.Write(b1) copy(b1, b2) // Mutate b1 to trigger potential race if err != nil { - if !os.IsTimeout(err) { + if !isDeadlineExceeded(err) { t.Error(err) } w.SetWriteDeadline(time.Now().Add(time.Millisecond))