Skip to content

Commit d422f54

Browse files
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 396833c commit d422f54

20 files changed

Lines changed: 196 additions & 108 deletions

doc/go1.15.html

Lines changed: 38 additions & 0 deletions
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

Lines changed: 11 additions & 7 deletions
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 2 deletions
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

Lines changed: 2 additions & 3 deletions
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

Lines changed: 7 additions & 8 deletions
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

Lines changed: 5 additions & 5 deletions
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)

0 commit comments

Comments
 (0)