Skip to content

Commit acc65b4

Browse files
author
Bryan C. Mills
committed
net: refactor TestWriteToTimeout
The test cases for this test had listed specific errors, but the specific error values were ignored in favor of just calling isDeadlineExceeded. Moreover, ENOBUFS errors (which can legitimately occur in the test if the network interface also happens to be saturated when the timeout occurs) were not handled at all. Now the test relies only on the timeout: we iterate until we have seen two of the expected timeout errors, and if we see ENOBUFS instead of "deadline exceeded" we back off to give the queues time to drain. Fixes #49930 Change-Id: I258a6d5c935d9635b02dffd79e197ba9caf83ac8 Reviewed-on: https://go-review.googlesource.com/c/go/+/370882 Trust: Bryan Mills <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 4b3d8d1 commit acc65b4

File tree

4 files changed

+64
-35
lines changed

4 files changed

+64
-35
lines changed

src/net/error_plan9_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ func isPlatformError(err error) bool {
1717
_, ok := err.(syscall.ErrorString)
1818
return ok
1919
}
20+
21+
func isENOBUFS(err error) bool {
22+
return false // ENOBUFS is Unix-specific
23+
}

src/net/error_unix_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package net
88

99
import (
10+
"errors"
1011
"os"
1112
"syscall"
1213
)
@@ -32,3 +33,7 @@ func samePlatformError(err, want error) bool {
3233
}
3334
return err == want
3435
}
36+
37+
func isENOBUFS(err error) bool {
38+
return errors.Is(err, syscall.ENOBUFS)
39+
}

src/net/error_windows_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
package net
66

7-
import "syscall"
7+
import (
8+
"errors"
9+
"syscall"
10+
)
811

912
var (
1013
errTimedout = syscall.ETIMEDOUT
@@ -17,3 +20,10 @@ func isPlatformError(err error) bool {
1720
_, ok := err.(syscall.Errno)
1821
return ok
1922
}
23+
24+
func isENOBUFS(err error) bool {
25+
// syscall.ENOBUFS is a completely made-up value on Windows: we don't expect
26+
// a real system call to ever actually return it. However, since it is already
27+
// defined in the syscall package we may as well check for it.
28+
return errors.Is(err, syscall.ENOBUFS)
29+
}

src/net/timeout_test.go

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -557,17 +557,6 @@ func TestWriteTimeoutMustNotReturn(t *testing.T) {
557557
}
558558
}
559559

560-
var writeToTimeoutTests = []struct {
561-
timeout time.Duration
562-
xerrs [2]error // expected errors in transition
563-
}{
564-
// Tests that write deadlines work, even if there's buffer
565-
// space available to write.
566-
{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
567-
568-
{10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
569-
}
570-
571560
func TestWriteToTimeout(t *testing.T) {
572561
t.Parallel()
573562

@@ -579,37 +568,58 @@ func TestWriteToTimeout(t *testing.T) {
579568
t.Fatal(err)
580569
}
581570

582-
for i, tt := range writeToTimeoutTests {
583-
c2, err := ListenPacket(c1.LocalAddr().Network(), JoinHostPort(host, "0"))
584-
if err != nil {
585-
t.Fatal(err)
586-
}
587-
defer c2.Close()
571+
timeouts := []time.Duration{
572+
-5 * time.Second,
573+
10 * time.Millisecond,
574+
}
588575

589-
if err := c2.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
590-
t.Fatalf("#%d: %v", i, err)
591-
}
592-
for j, xerr := range tt.xerrs {
593-
for {
576+
for _, timeout := range timeouts {
577+
t.Run(fmt.Sprint(timeout), func(t *testing.T) {
578+
c2, err := ListenPacket(c1.LocalAddr().Network(), JoinHostPort(host, "0"))
579+
if err != nil {
580+
t.Fatal(err)
581+
}
582+
defer c2.Close()
583+
584+
if err := c2.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
585+
t.Fatalf("SetWriteDeadline: %v", err)
586+
}
587+
backoff := 1 * time.Millisecond
588+
nDeadlineExceeded := 0
589+
for j := 0; nDeadlineExceeded < 2; j++ {
594590
n, err := c2.WriteTo([]byte("WRITETO TIMEOUT TEST"), c1.LocalAddr())
595-
if xerr != nil {
596-
if perr := parseWriteError(err); perr != nil {
597-
t.Errorf("#%d/%d: %v", i, j, perr)
598-
}
599-
if !isDeadlineExceeded(err) {
600-
t.Fatalf("#%d/%d: %v", i, j, err)
601-
}
591+
t.Logf("#%d: WriteTo: %d, %v", j, n, err)
592+
if err == nil && timeout >= 0 && nDeadlineExceeded == 0 {
593+
// If the timeout is nonnegative, some number of WriteTo calls may
594+
// succeed before the timeout takes effect.
595+
t.Logf("WriteTo succeeded; sleeping %v", timeout/3)
596+
time.Sleep(timeout / 3)
597+
continue
602598
}
603-
if err == nil {
604-
time.Sleep(tt.timeout / 3)
599+
if isENOBUFS(err) {
600+
t.Logf("WriteTo: %v", err)
601+
// We're looking for a deadline exceeded error, but if the kernel's
602+
// network buffers are saturated we may see ENOBUFS instead (see
603+
// https://go.dev/issue/49930). Give it some time to unsaturate.
604+
time.Sleep(backoff)
605+
backoff *= 2
605606
continue
606607
}
608+
if perr := parseWriteError(err); perr != nil {
609+
t.Errorf("failed to parse error: %v", perr)
610+
}
611+
if !isDeadlineExceeded(err) {
612+
t.Errorf("error is not 'deadline exceeded'")
613+
}
607614
if n != 0 {
608-
t.Fatalf("#%d/%d: wrote %d; want 0", i, j, n)
615+
t.Errorf("unexpectedly wrote %d bytes", n)
609616
}
610-
break
617+
if !t.Failed() {
618+
t.Logf("WriteTo timed out as expected")
619+
}
620+
nDeadlineExceeded++
611621
}
612-
}
622+
})
613623
}
614624
}
615625

0 commit comments

Comments
 (0)