]> Cypherpunks repositories - gostls13.git/commitdiff
os, net: define and use os.ErrDeadlineExceeded
authorIan Lance Taylor <iant@golang.org>
Fri, 17 Apr 2020 22:42:12 +0000 (15:42 -0700)
committerIan Lance Taylor <iant@golang.org>
Sat, 25 Apr 2020 00:26:48 +0000 (00:26 +0000)
If an I/O operation fails because a deadline was exceeded,
return os.ErrDeadlineExceeded. We used to return poll.ErrTimeout,
an internal error, and told users to check the Timeout method.
However, there are other errors with a Timeout method that returns true,
notably syscall.ETIMEDOUT which is returned for a keep-alive timeout.
Checking errors.Is(err, os.ErrDeadlineExceeded) should permit code
to reliably tell why it failed.

This change does not affect the handling of net.Dialer.Deadline,
nor does it change the handling of net.DialContext when the context
deadline is exceeded. Those cases continue to return an error
reported as "i/o timeout" for which Timeout is true, but that error
is not os.ErrDeadlineExceeded.

Fixes #31449

Change-Id: I0323f42e944324c6f2578f00c3ac90c24fe81177
Reviewed-on: https://go-review.googlesource.com/c/go/+/228645
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
20 files changed:
doc/go1.15.html
src/internal/poll/fd.go
src/internal/poll/fd_plan9.go
src/internal/poll/fd_poll_js.go
src/internal/poll/fd_poll_runtime.go
src/internal/poll/fd_windows.go
src/net/dial.go
src/net/dial_test.go
src/net/dnsclient_unix_test.go
src/net/error_test.go
src/net/net.go
src/net/pipe.go
src/net/rawconn_test.go
src/net/timeout_test.go
src/net/unixsock_test.go
src/os/error.go
src/os/file.go
src/os/os_test.go
src/os/os_unix_test.go
src/os/timeout_test.go

index a4f78c1c78a991fc1943b431dfb9733c6faed763..9d10092ffa7be4d99ffd4bb803995d5c1debde3f 100644 (file)
@@ -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 -->
index c0de50c1b46e55ba6c658dfa352ac1a2b67d17fb..b72ea3d55c2450ac216a7e599e1dc59ee741aec9 100644 (file)
@@ -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.
index 0fce32915e7dd41eda9b6627a295aa52ac6493f7..e57e0419c5532da591ddc9d6e3347005f7c3bd5b 100644 (file)
@@ -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
 }
index 2bfeb0a0b7af992f2f38cd0026800a6caf2b2056..d6b28e503cda489362f9c92c2430d3b538095170 100644 (file)
@@ -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) }
index fd73166ac3ec2329a21de2dfd49f520ac9e6cc51..222e5c6707afd08a4a9b96bdb0463631d4b205d9 100644 (file)
@@ -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
        }
index 1a0bdb34fec571b371dbdcdc8626feb3c29267cf..e1ef6199b37a44a1d9ba581a15d60463bcba4a2d 100644 (file)
@@ -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())
index d8be1c222d042f70b03bfe726a70f644da2c5d8a..13a312a91ae06b41a6a119d064a55b0a57bc2437 100644 (file)
@@ -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)
index aedf643e98d2255f32079afa5afc036106de8dae..01582489de12af4e64eb02c37b56c5c4a37411d1 100644 (file)
@@ -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)
index 2ad40dfe02b92b6a41732eb01ca5374dd52196fd..06553636eee253306d74cd943a11e782347183fc 100644 (file)
@@ -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}
 
index 89dcc2e6e6e1497b4b04413a0b2e07dcf5f49057..8d4a7ffb3d033b90f4b032811181b47d7ccf83c1 100644 (file)
@@ -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)
index 1d7e5e7f65253198a2400064cc76ac0597316236..82b71565aa6f0df16810bcfc9a78b543ce87bcf4 100644 (file)
@@ -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 {
index 9177fc403643e3b9de936811a9c1873efa5a34cf..f1741938b0bb8fc525dfd5cdeba9b793e481673b 100644 (file)
@@ -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
index 9a82f8f78e02b02cd3a2c3264a5bf688d45afbb8..a08ff89d1af8d9690c376e34ba3bd4bcac498670 100644 (file)
@@ -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)
                }
        })
index 51123dfbc46b44142029aebe4292de18323203b9..ad14cd79ac43caa12ef3523334c970dcfcc863c3 100644 (file)
@@ -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
+}
index 80cccf21e3d0fd5cac61388fdc8ece70c742b244..4b2cfc4d6283cf091697fe3af223c4e2daf70c55 100644 (file)
@@ -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)
                        }
                }
index 26bfe4cab5318487c66571950eb80c90e748b382..875cc9711f8f1e7187e2a98bf3d56b4738adc241 100644 (file)
@@ -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
 }
index 94341f90e2140a11b13887a01ca278ecf8577f32..57663005a122f2aa7eeb2e8a9ddcdca346a699f4 100644 (file)
@@ -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.
index 978e99110c08c9a9eb8db98d6a0f2764ad3cd6a7..f86428b7b9233ea5336a3d76f08057c05a881e36 100644 (file)
@@ -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
+}
index 45cb6fc21f2ee7f045786a072142bb384008ea8e..0bce2989c4a4130040141d8fbda2265fa0540349 100644 (file)
@@ -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 {
index 0fe03fa517f43a161b60b63f6dff99c04b2df73b..99b94c2e4cb0bfd1b21436472acd66028804e8a2 100644 (file)
@@ -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))