This change fixes inconsistent error values on Accept{,TCP,Unix}.
Updates #4856.
Change-Id: Ie3bb534c19a724cacb3ea3f3656e46c810b2123f
Reviewed-on: https://go-review.googlesource.com/8996
Reviewed-by: Ian Lance Taylor <iant@golang.org>
"os"
"runtime"
"testing"
+ "time"
)
func isTimeoutError(err error) bool {
}
}
}
+
+// parseAcceptError parses nestedErr and reports whether it is a valid
+// error value from Accept functions.
+// It returns nil when nestedErr is valid.
+func parseAcceptError(nestedErr error) error {
+ if nestedErr == nil {
+ return nil
+ }
+
+ switch err := nestedErr.(type) {
+ case *OpError:
+ if err := err.isValid(); err != nil {
+ return err
+ }
+ nestedErr = err.Err
+ goto second
+ }
+ return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
+
+second:
+ if isPlatformError(nestedErr) {
+ return nil
+ }
+ switch err := nestedErr.(type) {
+ case *os.SyscallError:
+ nestedErr = err.Err
+ goto third
+ }
+ switch nestedErr {
+ case errClosing, errTimeout:
+ return nil
+ }
+ return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
+
+third:
+ if isPlatformError(nestedErr) {
+ return nil
+ }
+ return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
+}
+
+func TestAcceptError(t *testing.T) {
+ handler := func(ls *localServer, ln Listener) {
+ for {
+ ln.(*TCPListener).SetDeadline(time.Now().Add(5 * time.Millisecond))
+ c, err := ln.Accept()
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
+ }
+ if err != nil {
+ if c != nil {
+ t.Errorf("Accept returned non-nil interface %T(%v) with err != nil", c, c)
+ }
+ if !isTimeoutError(err) && !isTemporaryError(err) {
+ return
+ }
+ continue
+ }
+ c.Close()
+ }
+ }
+ ls, err := newLocalServer("tcp")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := ls.buildup(handler); err != nil {
+ ls.teardown()
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond)
+ ls.teardown()
+}
var s int
var rsa syscall.Sockaddr
if err = fd.pd.PrepareRead(); err != nil {
- return nil, &OpError{"accept", fd.net, fd.laddr, err}
+ return nil, err
}
for {
s, rsa, err = accept(fd.sysfd)
// before we Accept()ed it; it's a silly error, so try again.
continue
}
- return nil, &OpError{"accept", fd.net, fd.laddr, err}
+ return nil, err
}
break
}
// Get new socket.
s, err := sysSocket(fd.family, fd.sotype, 0)
if err != nil {
- return nil, &OpError{"socket", fd.net, fd.laddr, err}
+ return nil, err
}
// Associate our new socket with IOCP.
netfd, err := newFD(s, fd.family, fd.sotype, fd.net)
if err != nil {
closeFunc(s)
- return nil, &OpError{"accept", fd.net, fd.laddr, err}
+ return nil, err
}
if err := netfd.init(); err != nil {
fd.Close()
err = syscall.Setsockopt(s, syscall.SOL_SOCKET, syscall.SO_UPDATE_ACCEPT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd)))
if err != nil {
netfd.Close()
- return nil, &OpError{"Setsockopt", fd.net, fd.laddr, err}
+ return nil, err
}
return netfd, nil
// before AcceptEx could complete. These errors relate to new
// connection, not to AcceptEx, so ignore broken connection and
// try AcceptEx again for more connections.
- operr, ok := err.(*OpError)
- if !ok {
- return nil, err
- }
- errno, ok := operr.Err.(syscall.Errno)
+ errno, ok := err.(syscall.Errno)
if !ok {
return nil, err
}
}
fd, err := l.fd.accept()
if err != nil {
- return nil, err
+ return nil, &OpError{Op: "accept", Net: l.fd.net, Addr: l.fd.laddr, Err: err}
}
return newTCPConn(fd), nil
}
if _, err := ln.Accept(); !isTimeoutError(err) {
t.Fatalf("Accept: expected err %v, got %v", errTimeout, err)
}
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
+ }
if _, err := ln.Accept(); !isTimeoutError(err) {
t.Fatalf("Accept: expected err %v, got %v", errTimeout, err)
}
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
+ }
ln.(*TCPListener).SetDeadline(time.Now().Add(100 * time.Millisecond))
if _, err := ln.Accept(); !isTimeoutError(err) {
t.Fatalf("Accept: expected err %v, got %v", errTimeout, err)
}
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
+ }
if _, err := ln.Accept(); !isTimeoutError(err) {
t.Fatalf("Accept: expected err %v, got %v", errTimeout, err)
}
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
+ }
ln.(*TCPListener).SetDeadline(noDeadline)
errc := make(chan error)
go func() {
default:
}
ln.Close()
- switch nerr := <-errc; err := nerr.(type) {
- case *OpError:
- if err.Err != errClosing {
- t.Fatalf("Accept: expected err %v, got %v", errClosing, err)
- }
- default:
- if err != errClosing {
- t.Fatalf("Accept: expected err %v, got %v", errClosing, err)
- }
+ err = <-errc
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
}
}
}
}
-func TestTimeoutAccept(t *testing.T) {
+func TestConcurrentAcceptTimeout(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("skipping test on %q", runtime.GOOS)
}
- ln, err := Listen("tcp", "127.0.0.1:0")
+
+ ln, err := newLocalListener("tcp")
if err != nil {
t.Fatal(err)
}
defer ln.Close()
- tl := ln.(*TCPListener)
- tl.SetDeadline(time.Now().Add(100 * time.Millisecond))
+ ln.(*TCPListener).SetDeadline(time.Now().Add(100 * time.Millisecond))
errc := make(chan error, 1)
go func() {
_, err := ln.Accept()
select {
case <-time.After(1 * time.Second):
// Accept shouldn't block indefinitely
- t.Errorf("Accept didn't return in an expected time")
- case <-errc:
- // Pass.
+ t.Error("Accept didn't return in an expected time")
+ case err := <-errc:
+ if perr := parseAcceptError(err); perr != nil {
+ t.Error(perr)
+ }
}
}
// AcceptUnix accepts the next incoming call and returns the new
// connection.
func (l *UnixListener) AcceptUnix() (*UnixConn, error) {
- return nil, syscall.EPLAN9
+ return nil, &OpError{Op: "accept", Net: "<nil>", Addr: nil, Err: syscall.EPLAN9}
}
// Accept implements the Accept method in the Listener interface; it
// waits for the next call and returns a generic Conn.
func (l *UnixListener) Accept() (Conn, error) {
- return nil, syscall.EPLAN9
+ return nil, &OpError{Op: "accept", Net: "<nil>", Addr: nil, Err: syscall.EPLAN9}
}
// Close stops listening on the Unix address. Already accepted
}
fd, err := l.fd.accept()
if err != nil {
- return nil, err
+ return nil, &OpError{Op: "accept", Net: l.fd.net, Addr: l.fd.laddr, Err: err}
}
- c := newUnixConn(fd)
- return c, nil
+ return newUnixConn(fd), nil
}
// Accept implements the Accept method in the Listener interface; it