Skip to content

Commit 67bf8ff

Browse files
ianlancetaylorxujianhai666
authored andcommitted
os, net: define and use os.ErrDeadlineExceeded
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 golang#31449 Change-Id: I0323f42e944324c6f2578f00c3ac90c24fe81177 Reviewed-on: https://go-review.googlesource.com/c/go/+/228645 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]>
1 parent 5daef82 commit 67bf8ff

20 files changed

+196
-108
lines changed

doc/go1.15.html

+38
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,25 @@ <h3 id="minor_library_changes">Minor changes to the library</h3>
172172
</dd>
173173
</dl>
174174

175+
<dl id="net"><dt><a href="/pkg/net/">net</a></dt>
176+
<dd>
177+
<p><!-- CL -->
178+
If an I/O operation exceeds a deadline set by
179+
the <a href="/pkg/net/#Conn"><code>Conn.SetDeadline</code></a>,
180+
<code>Conn.SetReadDeadline</code>,
181+
or <code>Conn.SetWriteDeadline</code> methods, it will now
182+
return an error that is or wraps
183+
<a href="/pkg/os#ErrDeadlineExceeded"><code>os.ErrDeadlineExceeded</code></a>.
184+
This may be used to reliably detect whether an error is due to
185+
an exceeded deadline.
186+
Earlier releases recommended calling the <code>Timeout</code>
187+
method on the error, but I/O operations can return errors for
188+
which <code>Timeout</code> returns <code>true</code> although a
189+
deadline has not been exceeded.
190+
</p>
191+
</dd>
192+
</dl>
193+
175194
<dl id="net/http/pprof"><dt><a href="/pkg/net/http/pprof/">net/http/pprof</a></dt>
176195
<dd>
177196
<p><!-- CL 147598, 229537 -->
@@ -200,6 +219,25 @@ <h3 id="minor_library_changes">Minor changes to the library</h3>
200219
</dd>
201220
</dl>
202221

222+
<dl id="os"><dt><a href="/pkg/os/">os</a></dt>
223+
<dd>
224+
<p><!-- CL -->
225+
If an I/O operation exceeds a deadline set by
226+
the <a href="/pkg/os/#File.SetDeadline"><code>File.SetDeadline</code></a>,
227+
<a href="/pkg/os/#File.SetReadDeadline"><code>File.SetReadDeadline</code></a>,
228+
or <a href="/pkg/os/#File.SetWriteDeadline"><code>File.SetWriteDeadline</code></a>
229+
methods, it will now return an error that is or wraps
230+
<a href="/pkg/os#ErrDeadlineExceeded"><code>os.ErrDeadlineExceeded</code></a>.
231+
This may be used to reliably detect whether an error is due to
232+
an exceeded deadline.
233+
Earlier releases recommended calling the <code>Timeout</code>
234+
method on the error, but I/O operations can return errors for
235+
which <code>Timeout</code> returns <code>true</code> although a
236+
deadline has not been exceeded.
237+
</p>
238+
</dd>
239+
</dl>
240+
203241
<dl id="reflect"><dt><a href="/pkg/reflect/">reflect</a></dt>
204242
<dd>
205243
<p><!-- CL 228902 -->

src/internal/poll/fd.go

+11-7
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,20 @@ func errClosing(isFile bool) error {
3535
return ErrNetClosing
3636
}
3737

38-
// ErrTimeout is returned for an expired deadline.
39-
var ErrTimeout error = &TimeoutError{}
38+
// ErrDeadlineExceeded is returned for an expired deadline.
39+
// This is exported by the os package as os.ErrDeadlineExceeded.
40+
var ErrDeadlineExceeded error = &DeadlineExceededError{}
4041

41-
// TimeoutError is returned for an expired deadline.
42-
type TimeoutError struct{}
42+
// DeadlineExceededError is returned for an expired deadline.
43+
type DeadlineExceededError struct{}
4344

4445
// Implement the net.Error interface.
45-
func (e *TimeoutError) Error() string { return "i/o timeout" }
46-
func (e *TimeoutError) Timeout() bool { return true }
47-
func (e *TimeoutError) Temporary() bool { return true }
46+
// The string is "i/o timeout" because that is what was returned
47+
// by earlier Go versions. Changing it may break programs that
48+
// match on error strings.
49+
func (e *DeadlineExceededError) Error() string { return "i/o timeout" }
50+
func (e *DeadlineExceededError) Timeout() bool { return true }
51+
func (e *DeadlineExceededError) Temporary() bool { return true }
4852

4953
// ErrNotPollable is returned when the file or socket is not suitable
5054
// for event notification.

src/internal/poll/fd_plan9.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (fd *FD) Close() error {
6060
// Read implements io.Reader.
6161
func (fd *FD) Read(fn func([]byte) (int, error), b []byte) (int, error) {
6262
if fd.rtimedout.isSet() {
63-
return 0, ErrTimeout
63+
return 0, ErrDeadlineExceeded
6464
}
6565
if err := fd.readLock(); err != nil {
6666
return 0, err
@@ -76,15 +76,15 @@ func (fd *FD) Read(fn func([]byte) (int, error), b []byte) (int, error) {
7676
err = io.EOF
7777
}
7878
if isInterrupted(err) {
79-
err = ErrTimeout
79+
err = ErrDeadlineExceeded
8080
}
8181
return n, err
8282
}
8383

8484
// Write implements io.Writer.
8585
func (fd *FD) Write(fn func([]byte) (int, error), b []byte) (int, error) {
8686
if fd.wtimedout.isSet() {
87-
return 0, ErrTimeout
87+
return 0, ErrDeadlineExceeded
8888
}
8989
if err := fd.writeLock(); err != nil {
9090
return 0, err
@@ -94,7 +94,7 @@ func (fd *FD) Write(fn func([]byte) (int, error), b []byte) (int, error) {
9494
n, err := fd.waio.Wait()
9595
fd.waio = nil
9696
if isInterrupted(err) {
97-
err = ErrTimeout
97+
err = ErrDeadlineExceeded
9898
}
9999
return n, err
100100
}

src/internal/poll/fd_poll_js.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (pd *pollDesc) wait(mode int, isFile bool) error {
4545
if isFile { // TODO(neelance): wasm: Use callbacks from JS to block until the read/write finished.
4646
return nil
4747
}
48-
return ErrTimeout
48+
return ErrDeadlineExceeded
4949
}
5050

5151
func (pd *pollDesc) waitRead(isFile bool) error { return pd.wait('r', isFile) }

src/internal/poll/fd_poll_runtime.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func convertErr(res int, isFile bool) error {
123123
case pollErrClosing:
124124
return errClosing(isFile)
125125
case pollErrTimeout:
126-
return ErrTimeout
126+
return ErrDeadlineExceeded
127127
case pollErrNotPollable:
128128
return ErrNotPollable
129129
}

src/internal/poll/fd_windows.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func execIO(o *operation, submit func(o *operation) error) (int, error) {
188188
// IO is interrupted by "close" or "timeout"
189189
netpollErr := err
190190
switch netpollErr {
191-
case ErrNetClosing, ErrFileClosing, ErrTimeout:
191+
case ErrNetClosing, ErrFileClosing, ErrDeadlineExceeded:
192192
// will deal with those.
193193
default:
194194
panic("unexpected runtime.netpoll error: " + netpollErr.Error())

src/net/dial.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package net
77
import (
88
"context"
99
"internal/nettrace"
10-
"internal/poll"
1110
"syscall"
1211
"time"
1312
)
@@ -141,7 +140,7 @@ func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, er
141140
}
142141
timeRemaining := deadline.Sub(now)
143142
if timeRemaining <= 0 {
144-
return time.Time{}, poll.ErrTimeout
143+
return time.Time{}, errTimeout
145144
}
146145
// Tentatively allocate equal time to each remaining address.
147146
timeout := timeRemaining / time.Duration(addrsRemaining)

src/net/dial_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package net
99
import (
1010
"bufio"
1111
"context"
12-
"internal/poll"
1312
"internal/testenv"
1413
"io"
1514
"os"
@@ -540,8 +539,8 @@ func TestDialerPartialDeadline(t *testing.T) {
540539
{now, noDeadline, 1, noDeadline, nil},
541540
// Step the clock forward and cross the deadline.
542541
{now.Add(-1 * time.Millisecond), now, 1, now, nil},
543-
{now.Add(0 * time.Millisecond), now, 1, noDeadline, poll.ErrTimeout},
544-
{now.Add(1 * time.Millisecond), now, 1, noDeadline, poll.ErrTimeout},
542+
{now.Add(0 * time.Millisecond), now, 1, noDeadline, errTimeout},
543+
{now.Add(1 * time.Millisecond), now, 1, noDeadline, errTimeout},
545544
}
546545
for i, tt := range testCases {
547546
deadline, err := partialDeadline(tt.now, tt.deadline, tt.addrs)

src/net/dnsclient_unix_test.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"context"
1111
"errors"
1212
"fmt"
13-
"internal/poll"
1413
"io/ioutil"
1514
"os"
1615
"path"
@@ -480,7 +479,7 @@ func TestGoLookupIPWithResolverConfig(t *testing.T) {
480479
break
481480
default:
482481
time.Sleep(10 * time.Millisecond)
483-
return dnsmessage.Message{}, poll.ErrTimeout
482+
return dnsmessage.Message{}, os.ErrDeadlineExceeded
484483
}
485484
r := dnsmessage.Message{
486485
Header: dnsmessage.Header{
@@ -993,7 +992,7 @@ func TestRetryTimeout(t *testing.T) {
993992
if s == "192.0.2.1:53" {
994993
deadline0 = deadline
995994
time.Sleep(10 * time.Millisecond)
996-
return dnsmessage.Message{}, poll.ErrTimeout
995+
return dnsmessage.Message{}, os.ErrDeadlineExceeded
997996
}
998997

999998
if deadline.Equal(deadline0) {
@@ -1131,7 +1130,7 @@ func TestStrictErrorsLookupIP(t *testing.T) {
11311130
}
11321131
makeTimeout := func() error {
11331132
return &DNSError{
1134-
Err: poll.ErrTimeout.Error(),
1133+
Err: os.ErrDeadlineExceeded.Error(),
11351134
Name: name,
11361135
Server: server,
11371136
IsTimeout: true,
@@ -1247,7 +1246,7 @@ func TestStrictErrorsLookupIP(t *testing.T) {
12471246
Questions: q.Questions,
12481247
}, nil
12491248
case resolveTimeout:
1250-
return dnsmessage.Message{}, poll.ErrTimeout
1249+
return dnsmessage.Message{}, os.ErrDeadlineExceeded
12511250
default:
12521251
t.Fatal("Impossible resolveWhich")
12531252
}
@@ -1372,7 +1371,7 @@ func TestStrictErrorsLookupTXT(t *testing.T) {
13721371

13731372
switch q.Questions[0].Name.String() {
13741373
case searchX:
1375-
return dnsmessage.Message{}, poll.ErrTimeout
1374+
return dnsmessage.Message{}, os.ErrDeadlineExceeded
13761375
case searchY:
13771376
return mockTXTResponse(q), nil
13781377
default:
@@ -1387,7 +1386,7 @@ func TestStrictErrorsLookupTXT(t *testing.T) {
13871386
var wantRRs int
13881387
if strict {
13891388
wantErr = &DNSError{
1390-
Err: poll.ErrTimeout.Error(),
1389+
Err: os.ErrDeadlineExceeded.Error(),
13911390
Name: name,
13921391
Server: server,
13931392
IsTimeout: true,
@@ -1415,7 +1414,7 @@ func TestDNSGoroutineRace(t *testing.T) {
14151414

14161415
fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) {
14171416
time.Sleep(10 * time.Microsecond)
1418-
return dnsmessage.Message{}, poll.ErrTimeout
1417+
return dnsmessage.Message{}, os.ErrDeadlineExceeded
14191418
}}
14201419
r := Resolver{PreferGo: true, Dial: fake.DialContext}
14211420

src/net/error_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ second:
9191
return nil
9292
}
9393
switch err := nestedErr.(type) {
94-
case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *poll.TimeoutError, UnknownNetworkError:
94+
case *AddrError, addrinfoErrno, *timeoutError, *DNSError, InvalidAddrError, *ParseError, *poll.DeadlineExceededError, UnknownNetworkError:
9595
return nil
9696
case *os.SyscallError:
9797
nestedErr = err.Err
@@ -436,7 +436,7 @@ second:
436436
goto third
437437
}
438438
switch nestedErr {
439-
case poll.ErrNetClosing, poll.ErrTimeout, poll.ErrNotPollable:
439+
case poll.ErrNetClosing, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
440440
return nil
441441
}
442442
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -471,14 +471,14 @@ second:
471471
return nil
472472
}
473473
switch err := nestedErr.(type) {
474-
case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *poll.TimeoutError, UnknownNetworkError:
474+
case *AddrError, addrinfoErrno, *timeoutError, *DNSError, InvalidAddrError, *ParseError, *poll.DeadlineExceededError, UnknownNetworkError:
475475
return nil
476476
case *os.SyscallError:
477477
nestedErr = err.Err
478478
goto third
479479
}
480480
switch nestedErr {
481-
case errCanceled, poll.ErrNetClosing, errMissingAddress, poll.ErrTimeout, ErrWriteToConnected, io.ErrUnexpectedEOF:
481+
case errCanceled, poll.ErrNetClosing, errMissingAddress, errTimeout, os.ErrDeadlineExceeded, ErrWriteToConnected, io.ErrUnexpectedEOF:
482482
return nil
483483
}
484484
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -627,7 +627,7 @@ second:
627627
goto third
628628
}
629629
switch nestedErr {
630-
case poll.ErrNetClosing, poll.ErrTimeout, poll.ErrNotPollable:
630+
case poll.ErrNetClosing, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
631631
return nil
632632
}
633633
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)

src/net/net.go

+27-14
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ package net
8181
import (
8282
"context"
8383
"errors"
84-
"internal/poll"
8584
"io"
8685
"os"
8786
"sync"
@@ -136,23 +135,22 @@ type Conn interface {
136135
// SetReadDeadline and SetWriteDeadline.
137136
//
138137
// A deadline is an absolute time after which I/O operations
139-
// fail with a timeout (see type Error) instead of
140-
// blocking. The deadline applies to all future and pending
141-
// I/O, not just the immediately following call to Read or
142-
// Write. After a deadline has been exceeded, the connection
143-
// can be refreshed by setting a deadline in the future.
138+
// fail instead of blocking. The deadline applies to all future
139+
// and pending I/O, not just the immediately following call to
140+
// Read or Write. After a deadline has been exceeded, the
141+
// connection can be refreshed by setting a deadline in the future.
142+
//
143+
// If the deadline is exceeded a call to Read or Write or to other
144+
// I/O methods will return an error that wraps os.ErrDeadlineExceeded.
145+
// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
146+
// The error's Timeout method will return true, but note that there
147+
// are other possible errors for which the Timeout method will
148+
// return true even if the deadline has not been exceeded.
144149
//
145150
// An idle timeout can be implemented by repeatedly extending
146151
// the deadline after successful Read or Write calls.
147152
//
148153
// A zero value for t means I/O operations will not time out.
149-
//
150-
// Note that if a TCP connection has keep-alive turned on,
151-
// which is the default unless overridden by Dialer.KeepAlive
152-
// or ListenConfig.KeepAlive, then a keep-alive failure may
153-
// also return a timeout error. On Unix systems a keep-alive
154-
// failure on I/O can be detected using
155-
// errors.Is(err, syscall.ETIMEDOUT).
156154
SetDeadline(t time.Time) error
157155

158156
// SetReadDeadline sets the deadline for future Read calls
@@ -420,7 +418,7 @@ func mapErr(err error) error {
420418
case context.Canceled:
421419
return errCanceled
422420
case context.DeadlineExceeded:
423-
return poll.ErrTimeout
421+
return errTimeout
424422
default:
425423
return err
426424
}
@@ -567,6 +565,21 @@ func (e InvalidAddrError) Error() string { return string(e) }
567565
func (e InvalidAddrError) Timeout() bool { return false }
568566
func (e InvalidAddrError) Temporary() bool { return false }
569567

568+
// errTimeout exists to return the historical "i/o timeout" string
569+
// for context.DeadlineExceeded. See mapErr.
570+
// It is also used when Dialer.Deadline is exceeded.
571+
//
572+
// TODO(iant): We could consider changing this to os.ErrDeadlineExceeded
573+
// in the future, but note that that would conflict with the TODO
574+
// at mapErr that suggests changing it to context.DeadlineExceeded.
575+
var errTimeout error = &timeoutError{}
576+
577+
type timeoutError struct{}
578+
579+
func (e *timeoutError) Error() string { return "i/o timeout" }
580+
func (e *timeoutError) Timeout() bool { return true }
581+
func (e *timeoutError) Temporary() bool { return true }
582+
570583
// DNSConfigError represents an error reading the machine's DNS configuration.
571584
// (No longer used; kept for compatibility.)
572585
type DNSConfigError struct {

0 commit comments

Comments
 (0)