Skip to content

Commit 5e774b0

Browse files
author
Bryan C. Mills
committed
net: simplify deadline fluctuation tests
These tests were checking for fairly narrow timing windows, but were running in parallel and heavily dependent on timer and goroutine scheduling. This change eliminates unnecessary goroutines, runs the tests sequentially (dramatically shortening the timeouts to reduce the penalty of doing so), and uses timestamp comparison instead of background timers to hopefully gain some robustness from monotonic timestamps. Many of the other tests from this package would benefit from similar simplifications, which we can apply if and when we notice flaky failures or want to improve the latency of running the test. Fixes #36108 Change-Id: I17d8af7d2eefb1ec14fe0d9d891142a39599a562 Reviewed-on: https://go-review.googlesource.com/c/go/+/365334 Trust: Bryan C. Mills <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent 6027b21 commit 5e774b0

File tree

2 files changed

+141
-108
lines changed

2 files changed

+141
-108
lines changed

src/net/mockserver_test.go

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"fmt"
1212
"os"
1313
"sync"
14-
"testing"
1514
"time"
1615
)
1716

@@ -287,54 +286,6 @@ func transceiver(c Conn, wb []byte, ch chan<- error) {
287286
}
288287
}
289288

290-
func timeoutReceiver(c Conn, d, min, max time.Duration, ch chan<- error) {
291-
var err error
292-
defer func() { ch <- err }()
293-
294-
t0 := time.Now()
295-
if err = c.SetReadDeadline(time.Now().Add(d)); err != nil {
296-
return
297-
}
298-
b := make([]byte, 256)
299-
var n int
300-
n, err = c.Read(b)
301-
t1 := time.Now()
302-
if n != 0 || err == nil || !err.(Error).Timeout() {
303-
err = fmt.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
304-
return
305-
}
306-
if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() {
307-
err = fmt.Errorf("Read took %s; expected %s", dt, d)
308-
return
309-
}
310-
}
311-
312-
func timeoutTransmitter(c Conn, d, min, max time.Duration, ch chan<- error) {
313-
var err error
314-
defer func() { ch <- err }()
315-
316-
t0 := time.Now()
317-
if err = c.SetWriteDeadline(time.Now().Add(d)); err != nil {
318-
return
319-
}
320-
var n int
321-
for {
322-
n, err = c.Write([]byte("TIMEOUT TRANSMITTER"))
323-
if err != nil {
324-
break
325-
}
326-
}
327-
t1 := time.Now()
328-
if err == nil || !err.(Error).Timeout() {
329-
err = fmt.Errorf("Write did not return (any, timeout): (%d, %v)", n, err)
330-
return
331-
}
332-
if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() {
333-
err = fmt.Errorf("Write took %s; expected %s", dt, d)
334-
return
335-
}
336-
}
337-
338289
func newLocalPacketListener(network string) (PacketConn, error) {
339290
switch network {
340291
case "udp":
@@ -504,25 +455,3 @@ func packetTransceiver(c PacketConn, wb []byte, dst Addr, ch chan<- error) {
504455
ch <- fmt.Errorf("read %d; want %d", n, len(wb))
505456
}
506457
}
507-
508-
func timeoutPacketReceiver(c PacketConn, d, min, max time.Duration, ch chan<- error) {
509-
var err error
510-
defer func() { ch <- err }()
511-
512-
t0 := time.Now()
513-
if err = c.SetReadDeadline(time.Now().Add(d)); err != nil {
514-
return
515-
}
516-
b := make([]byte, 256)
517-
var n int
518-
n, _, err = c.ReadFrom(b)
519-
t1 := time.Now()
520-
if n != 0 || err == nil || !err.(Error).Timeout() {
521-
err = fmt.Errorf("ReadFrom did not return (0, timeout): (%d, %v)", n, err)
522-
return
523-
}
524-
if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() {
525-
err = fmt.Errorf("ReadFrom took %s; expected %s", dt, d)
526-
return
527-
}
528-
}

src/net/timeout_test.go

Lines changed: 141 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -643,9 +643,24 @@ func TestWriteToTimeout(t *testing.T) {
643643
}
644644
}
645645

646-
func TestReadTimeoutFluctuation(t *testing.T) {
647-
t.Parallel()
646+
const (
647+
// minDynamicTimeout is the minimum timeout to attempt for
648+
// tests that automatically increase timeouts until success.
649+
//
650+
// Lower values may allow tests to succeed more quickly if the value is close
651+
// to the true minimum, but may require more iterations (and waste more time
652+
// and CPU power on failed attempts) if the timeout is too low.
653+
minDynamicTimeout = 1 * time.Millisecond
654+
655+
// maxDynamicTimeout is the maximum timeout to attempt for
656+
// tests that automatically increase timeouts until succeess.
657+
//
658+
// This should be a strict upper bound on the latency of the timeout: if a
659+
// test would increase the timeout beyond this value, the test fails.
660+
maxDynamicTimeout = 1 * time.Second
661+
)
648662

663+
func TestReadTimeoutFluctuation(t *testing.T) {
649664
ln, err := newLocalListener("tcp")
650665
if err != nil {
651666
t.Fatal(err)
@@ -658,27 +673,52 @@ func TestReadTimeoutFluctuation(t *testing.T) {
658673
}
659674
defer c.Close()
660675

661-
max := time.NewTimer(time.Second)
662-
defer max.Stop()
663-
ch := make(chan error)
664-
go timeoutReceiver(c, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch)
676+
d := minDynamicTimeout
677+
b := make([]byte, 256)
678+
for {
679+
t.Logf("SetReadDeadline(+%v)", d)
680+
t0 := time.Now()
681+
deadline := t0.Add(d)
682+
if err = c.SetReadDeadline(deadline); err != nil {
683+
t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
684+
}
685+
var n int
686+
n, err = c.Read(b)
687+
t1 := time.Now()
665688

666-
select {
667-
case <-max.C:
668-
t.Fatal("Read took over 1s; expected 0.1s")
669-
case err := <-ch:
689+
if n != 0 || err == nil || !err.(Error).Timeout() {
690+
t.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
691+
}
670692
if perr := parseReadError(err); perr != nil {
671693
t.Error(perr)
672694
}
673695
if !isDeadlineExceeded(err) {
674-
t.Fatal(err)
696+
t.Errorf("Read error is not DeadlineExceeded: %v", err)
697+
}
698+
699+
actual := t1.Sub(t0)
700+
if t1.Before(deadline) {
701+
t.Errorf("Read took %s; expected at least %s", actual, d)
675702
}
703+
if t.Failed() {
704+
return
705+
}
706+
if actual > d*11/10 {
707+
if actual > maxDynamicTimeout || d > maxDynamicTimeout/2 {
708+
t.Fatalf("Read took %s; expected %v", actual, d)
709+
}
710+
// Maybe this machine is too slow to reliably schedule goroutines within
711+
// the requested duration. Increase the timeout and try again.
712+
t.Logf("Read took %s (expected %s); trying with longer timeout", actual, d)
713+
d *= 2
714+
continue
715+
}
716+
717+
break
676718
}
677719
}
678720

679721
func TestReadFromTimeoutFluctuation(t *testing.T) {
680-
t.Parallel()
681-
682722
c1, err := newLocalPacketListener("udp")
683723
if err != nil {
684724
t.Fatal(err)
@@ -691,27 +731,52 @@ func TestReadFromTimeoutFluctuation(t *testing.T) {
691731
}
692732
defer c2.Close()
693733

694-
max := time.NewTimer(time.Second)
695-
defer max.Stop()
696-
ch := make(chan error)
697-
go timeoutPacketReceiver(c2.(PacketConn), 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch)
734+
d := minDynamicTimeout
735+
b := make([]byte, 256)
736+
for {
737+
t.Logf("SetReadDeadline(+%v)", d)
738+
t0 := time.Now()
739+
deadline := t0.Add(d)
740+
if err = c2.SetReadDeadline(deadline); err != nil {
741+
t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
742+
}
743+
var n int
744+
n, _, err = c2.(PacketConn).ReadFrom(b)
745+
t1 := time.Now()
698746

699-
select {
700-
case <-max.C:
701-
t.Fatal("ReadFrom took over 1s; expected 0.1s")
702-
case err := <-ch:
747+
if n != 0 || err == nil || !err.(Error).Timeout() {
748+
t.Errorf("ReadFrom did not return (0, timeout): (%d, %v)", n, err)
749+
}
703750
if perr := parseReadError(err); perr != nil {
704751
t.Error(perr)
705752
}
706753
if !isDeadlineExceeded(err) {
707-
t.Fatal(err)
754+
t.Errorf("ReadFrom error is not DeadlineExceeded: %v", err)
708755
}
756+
757+
actual := t1.Sub(t0)
758+
if t1.Before(deadline) {
759+
t.Errorf("ReadFrom took %s; expected at least %s", actual, d)
760+
}
761+
if t.Failed() {
762+
return
763+
}
764+
if actual > d*11/10 {
765+
if actual > maxDynamicTimeout || d > maxDynamicTimeout/2 {
766+
t.Fatalf("ReadFrom took %s; expected %s", actual, d)
767+
}
768+
// Maybe this machine is too slow to reliably schedule goroutines within
769+
// the requested duration. Increase the timeout and try again.
770+
t.Logf("ReadFrom took %s (expected %s); trying with longer timeout", actual, d)
771+
d *= 2
772+
continue
773+
}
774+
775+
break
709776
}
710777
}
711778

712779
func TestWriteTimeoutFluctuation(t *testing.T) {
713-
t.Parallel()
714-
715780
switch runtime.GOOS {
716781
case "plan9":
717782
t.Skipf("not supported on %s", runtime.GOOS)
@@ -729,25 +794,64 @@ func TestWriteTimeoutFluctuation(t *testing.T) {
729794
}
730795
defer c.Close()
731796

732-
d := time.Second
733-
if iOS() {
734-
d = 3 * time.Second // see golang.org/issue/10775
735-
}
736-
max := time.NewTimer(d)
737-
defer max.Stop()
738-
ch := make(chan error)
739-
go timeoutTransmitter(c, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch)
797+
d := minDynamicTimeout
798+
for {
799+
t.Logf("SetWriteDeadline(+%v)", d)
800+
t0 := time.Now()
801+
deadline := t0.Add(d)
802+
if err = c.SetWriteDeadline(deadline); err != nil {
803+
t.Fatalf("SetWriteDeadline(%v): %v", deadline, err)
804+
}
805+
var n int64
806+
for {
807+
var dn int
808+
dn, err = c.Write([]byte("TIMEOUT TRANSMITTER"))
809+
n += int64(dn)
810+
if err != nil {
811+
break
812+
}
813+
}
814+
t1 := time.Now()
740815

741-
select {
742-
case <-max.C:
743-
t.Fatalf("Write took over %v; expected 0.1s", d)
744-
case err := <-ch:
816+
if err == nil || !err.(Error).Timeout() {
817+
t.Fatalf("Write did not return (any, timeout): (%d, %v)", n, err)
818+
}
745819
if perr := parseWriteError(err); perr != nil {
746820
t.Error(perr)
747821
}
748822
if !isDeadlineExceeded(err) {
749-
t.Fatal(err)
823+
t.Errorf("Write error is not DeadlineExceeded: %v", err)
750824
}
825+
826+
actual := t1.Sub(t0)
827+
if t1.Before(deadline) {
828+
t.Errorf("Write took %s; expected at least %s", actual, d)
829+
}
830+
if t.Failed() {
831+
return
832+
}
833+
if actual > d*11/10 {
834+
if n > 0 {
835+
// SetWriteDeadline specifies a time “after which I/O operations fail
836+
// instead of blocking”. However, the kernel's send buffer is not yet
837+
// full, we may be able to write some arbitrary (but finite) number of
838+
// bytes to it without blocking.
839+
t.Logf("Wrote %d bytes into send buffer; retrying until buffer is full", n)
840+
if d <= maxDynamicTimeout/2 {
841+
d *= 2
842+
}
843+
} else if actual > maxDynamicTimeout || d > maxDynamicTimeout/2 {
844+
t.Fatalf("Write took %s; expected %s", actual, d)
845+
} else {
846+
// Maybe this machine is too slow to reliably schedule goroutines within
847+
// the requested duration. Increase the timeout and try again.
848+
t.Logf("Write took %s (expected %s); trying with longer timeout", actual, d)
849+
d *= 2
850+
}
851+
continue
852+
}
853+
854+
break
751855
}
752856
}
753857

0 commit comments

Comments
 (0)