net: export ErrClosed
This permits programs to reliably detect whether they are using a
closed network connection.
Fixes #4373
Change-Id: Ib4ce8cc82bbb134c4689f0ebc8b9b11bb8b32a22
Reviewed-on: https://go-review.googlesource.com/c/go/+/250357
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/doc/go1.16.html b/doc/go1.16.html
index b11af7f..c82b3b9 100644
--- a/doc/go1.16.html
+++ b/doc/go1.16.html
@@ -99,6 +99,18 @@
TODO
</p>
+<h3 id="net"><a href="/pkg/net/">net</a></h3>
+
+<p><!-- CL -->
+ The case of I/O on a closed network connection, or I/O on a network
+ connection that is closed before any of the I/O completes, can now
+ be detected using the new <a href="/pkg/net/#ErrClosed">ErrClosed</a> error.
+ A typical use would be <code>errors.Is(err, net.ErrClosed)</code>.
+ In earlier releases the only way to reliably detect this case was to
+ match the string returned by the <code>Error</code> method
+ with <code>"use of closed network connection"</code>.
+</p>
+
<h3 id="unicode"><a href="/pkg/unicode/">unicode</a></h3>
<p><!-- CL 248765 -->
diff --git a/src/net/error_test.go b/src/net/error_test.go
index 8d4a7ff..62dfb9c 100644
--- a/src/net/error_test.go
+++ b/src/net/error_test.go
@@ -8,6 +8,7 @@
import (
"context"
+ "errors"
"fmt"
"internal/poll"
"io"
@@ -101,7 +102,7 @@
goto third
}
switch nestedErr {
- case errCanceled, poll.ErrNetClosing, errMissingAddress, errNoSuitableAddress,
+ case errCanceled, ErrClosed, errMissingAddress, errNoSuitableAddress,
context.DeadlineExceeded, context.Canceled:
return nil
}
@@ -436,7 +437,7 @@
goto third
}
switch nestedErr {
- case poll.ErrNetClosing, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
+ case ErrClosed, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -478,7 +479,7 @@
goto third
}
switch nestedErr {
- case errCanceled, poll.ErrNetClosing, errMissingAddress, errTimeout, os.ErrDeadlineExceeded, ErrWriteToConnected, io.ErrUnexpectedEOF:
+ case errCanceled, ErrClosed, errMissingAddress, errTimeout, os.ErrDeadlineExceeded, ErrWriteToConnected, io.ErrUnexpectedEOF:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -508,6 +509,10 @@
return fmt.Errorf("error string %q does not contain expected string %q", nestedErr, want)
}
+ if !isShutdown && !errors.Is(nestedErr, ErrClosed) {
+ return fmt.Errorf("errors.Is(%v, errClosed) returns false, want true", nestedErr)
+ }
+
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
@@ -531,7 +536,7 @@
goto third
}
switch nestedErr {
- case poll.ErrNetClosing:
+ case ErrClosed:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -627,7 +632,7 @@
goto third
}
switch nestedErr {
- case poll.ErrNetClosing, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
+ case ErrClosed, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
@@ -706,7 +711,7 @@
goto third
}
switch nestedErr {
- case poll.ErrNetClosing:
+ case ErrClosed:
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 2e61a7c..4b4ed12 100644
--- a/src/net/net.go
+++ b/src/net/net.go
@@ -81,6 +81,7 @@
import (
"context"
"errors"
+ "internal/poll"
"io"
"os"
"sync"
@@ -632,6 +633,17 @@
// error and return a DNSError for which Temporary returns false.
func (e *DNSError) Temporary() bool { return e.IsTimeout || e.IsTemporary }
+// errClosed exists just so that the docs for ErrClosed don't mention
+// the internal package poll.
+var errClosed = poll.ErrNetClosing
+
+// ErrClosed is the error returned by an I/O call on a network
+// connection that has already been closed, or that is closed by
+// another goroutine before the I/O is completed. This may be wrapped
+// in another error, and should normally be tested using
+// errors.Is(err, net.ErrClosed).
+var ErrClosed = errClosed
+
type writerOnly struct {
io.Writer
}