mirror of
https://github.com/golang/go.git
synced 2025-05-05 07:33:00 +00:00
os: test overlapped pipes deadlines on Windows
NewFile recently added support for overlapped I/O on Windows, which allows us to set deadlines on them, but the test coverage for this new feature is not exhaustive. Modify the existing pipe deadline tests to also exercise named overlapped pipes. Updates #19098. Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-race,gotip-windows-amd64-longtest,gotip-windows-arm64 Change-Id: I86d284d9fb054c24959045a922cf84feeda5b5f0 Reviewed-on: https://go-review.googlesource.com/c/go/+/668095 Reviewed-by: Alex Brainman <alex.brainman@gmail.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Carlos Amedee <carlos@golang.org> Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
parent
06751c455d
commit
3cebfb678b
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !js && !plan9 && !wasip1 && !windows
|
||||
//go:build !js && !plan9 && !wasip1
|
||||
|
||||
package os_test
|
||||
|
||||
@ -11,18 +11,16 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNonpollableDeadline(t *testing.T) {
|
||||
// On BSD systems regular files seem to be pollable,
|
||||
// so just run this test on Linux.
|
||||
if runtime.GOOS != "linux" {
|
||||
// so just run this test on Linux and Windows.
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
t.Parallel()
|
||||
@ -45,6 +43,13 @@ func TestNonpollableDeadline(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type pipeDeadlineTest struct {
|
||||
name string
|
||||
create func(t *testing.T) (r, w *os.File)
|
||||
}
|
||||
|
||||
var pipeDeadlinesTestCases []pipeDeadlineTest
|
||||
|
||||
// noDeadline is a zero time.Time value, which cancels a deadline.
|
||||
var noDeadline time.Time
|
||||
|
||||
@ -63,40 +68,43 @@ var readTimeoutTests = []struct {
|
||||
func TestReadTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
for i, tt := range readTimeoutTests {
|
||||
if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil {
|
||||
t.Fatalf("#%d: %v", i, err)
|
||||
}
|
||||
var b [1]byte
|
||||
for j, xerr := range tt.xerrs {
|
||||
for {
|
||||
n, err := r.Read(b[:])
|
||||
if xerr != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Fatalf("#%d/%d: %v", i, j, err)
|
||||
if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, tt := range readTimeoutTests {
|
||||
if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil {
|
||||
t.Fatalf("#%d: %v", i, err)
|
||||
}
|
||||
var b [1]byte
|
||||
for j, xerr := range tt.xerrs {
|
||||
for {
|
||||
n, err := r.Read(b[:])
|
||||
if xerr != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Fatalf("#%d/%d: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
time.Sleep(tt.timeout / 3)
|
||||
continue
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("#%d/%d: read %d; want 0", i, j, n)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
time.Sleep(tt.timeout / 3)
|
||||
continue
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("#%d/%d: read %d; want 0", i, j, n)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,40 +112,43 @@ func TestReadTimeout(t *testing.T) {
|
||||
func TestReadTimeoutMustNotReturn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
max := time.NewTimer(100 * time.Millisecond)
|
||||
defer max.Stop()
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := r.SetReadDeadline(noDeadline); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var b [1]byte
|
||||
_, err := r.Read(b[:])
|
||||
ch <- err
|
||||
}()
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
t.Fatalf("expected Read to not return, but it returned with %v", err)
|
||||
case <-max.C:
|
||||
w.Close()
|
||||
err := <-ch // wait for tester goroutine to stop
|
||||
if os.IsTimeout(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
max := time.NewTimer(100 * time.Millisecond)
|
||||
defer max.Stop()
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := r.SetReadDeadline(noDeadline); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var b [1]byte
|
||||
_, err := r.Read(b[:])
|
||||
ch <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
t.Fatalf("expected Read to not return, but it returned with %v", err)
|
||||
case <-max.C:
|
||||
w.Close()
|
||||
err := <-ch // wait for tester goroutine to stop
|
||||
if os.IsTimeout(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,35 +167,38 @@ var writeTimeoutTests = []struct {
|
||||
func TestWriteTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i, tt := range writeTimeoutTests {
|
||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
for j, xerr := range tt.xerrs {
|
||||
for {
|
||||
n, err := w.Write([]byte("WRITE TIMEOUT TEST"))
|
||||
if xerr != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Fatalf("%d: %v", j, err)
|
||||
for i, tt := range writeTimeoutTests {
|
||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
for j, xerr := range tt.xerrs {
|
||||
for {
|
||||
n, err := w.Write([]byte("WRITE TIMEOUT TEST"))
|
||||
if xerr != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Fatalf("%d: %v", j, err)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
time.Sleep(tt.timeout / 3)
|
||||
continue
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("%d: wrote %d; want 0", j, n)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
time.Sleep(tt.timeout / 3)
|
||||
continue
|
||||
}
|
||||
if n != 0 {
|
||||
t.Fatalf("%d: wrote %d; want 0", j, n)
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -194,44 +208,47 @@ func TestWriteTimeout(t *testing.T) {
|
||||
func TestWriteTimeoutMustNotReturn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
max := time.NewTimer(100 * time.Millisecond)
|
||||
defer max.Stop()
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := w.SetWriteDeadline(noDeadline); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var b [1]byte
|
||||
for {
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
ch <- err
|
||||
break
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
max := time.NewTimer(100 * time.Millisecond)
|
||||
defer max.Stop()
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := w.SetWriteDeadline(noDeadline); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var b [1]byte
|
||||
for {
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
ch <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
t.Fatalf("expected Write to not return, but it returned with %v", err)
|
||||
case <-max.C:
|
||||
r.Close()
|
||||
err := <-ch // wait for tester goroutine to stop
|
||||
if os.IsTimeout(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-ch:
|
||||
t.Fatalf("expected Write to not return, but it returned with %v", err)
|
||||
case <-max.C:
|
||||
r.Close()
|
||||
err := <-ch // wait for tester goroutine to stop
|
||||
if os.IsTimeout(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,50 +306,53 @@ func nextTimeout(actual time.Duration) (next time.Duration, ok bool) {
|
||||
func TestReadTimeoutFluctuation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := minDynamicTimeout
|
||||
b := make([]byte, 256)
|
||||
for {
|
||||
t.Logf("SetReadDeadline(+%v)", d)
|
||||
t0 := time.Now()
|
||||
deadline := t0.Add(d)
|
||||
if err = r.SetReadDeadline(deadline); err != nil {
|
||||
t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
|
||||
}
|
||||
var n int
|
||||
n, err = r.Read(b)
|
||||
t1 := time.Now()
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
if n != 0 || err == nil || !isDeadlineExceeded(err) {
|
||||
t.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
|
||||
}
|
||||
d := minDynamicTimeout
|
||||
b := make([]byte, 256)
|
||||
for {
|
||||
t.Logf("SetReadDeadline(+%v)", d)
|
||||
t0 := time.Now()
|
||||
deadline := t0.Add(d)
|
||||
if err := r.SetReadDeadline(deadline); err != nil {
|
||||
t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
|
||||
}
|
||||
var n int
|
||||
n, err := r.Read(b)
|
||||
t1 := time.Now()
|
||||
|
||||
actual := t1.Sub(t0)
|
||||
if t1.Before(deadline) {
|
||||
t.Errorf("Read took %s; expected at least %s", actual, d)
|
||||
}
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
if want := timeoutUpperBound(d); actual > want {
|
||||
next, ok := nextTimeout(actual)
|
||||
if !ok {
|
||||
t.Fatalf("Read took %s; expected at most %v", actual, want)
|
||||
if n != 0 || err == nil || !isDeadlineExceeded(err) {
|
||||
t.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
|
||||
}
|
||||
|
||||
actual := t1.Sub(t0)
|
||||
if t1.Before(deadline) {
|
||||
t.Errorf("Read took %s; expected at least %s", actual, d)
|
||||
}
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
if want := timeoutUpperBound(d); actual > want {
|
||||
next, ok := nextTimeout(actual)
|
||||
if !ok {
|
||||
t.Fatalf("Read took %s; expected at most %v", actual, want)
|
||||
}
|
||||
// Maybe this machine is too slow to reliably schedule goroutines within
|
||||
// the requested duration. Increase the timeout and try again.
|
||||
t.Logf("Read took %s (expected %s); trying with longer timeout", actual, d)
|
||||
d = next
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
// Maybe this machine is too slow to reliably schedule goroutines within
|
||||
// the requested duration. Increase the timeout and try again.
|
||||
t.Logf("Read took %s (expected %s); trying with longer timeout", actual, d)
|
||||
d = next
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,76 +360,84 @@ func TestReadTimeoutFluctuation(t *testing.T) {
|
||||
func TestWriteTimeoutFluctuation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
d := minDynamicTimeout
|
||||
for {
|
||||
t.Logf("SetWriteDeadline(+%v)", d)
|
||||
t0 := time.Now()
|
||||
deadline := t0.Add(d)
|
||||
if err := w.SetWriteDeadline(deadline); err != nil {
|
||||
t.Fatalf("SetWriteDeadline(%v): %v", deadline, err)
|
||||
}
|
||||
var n int64
|
||||
var err error
|
||||
for {
|
||||
var dn int
|
||||
dn, err = w.Write([]byte("TIMEOUT TRANSMITTER"))
|
||||
n += int64(dn)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
t1 := time.Now()
|
||||
// Inv: err != nil
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Fatalf("Write did not return (any, timeout): (%d, %v)", n, err)
|
||||
}
|
||||
|
||||
actual := t1.Sub(t0)
|
||||
if t1.Before(deadline) {
|
||||
t.Errorf("Write took %s; expected at least %s", actual, d)
|
||||
}
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
if want := timeoutUpperBound(d); actual > want {
|
||||
if n > 0 {
|
||||
// SetWriteDeadline specifies a time “after which I/O operations fail
|
||||
// instead of blocking”. However, the kernel's send buffer is not yet
|
||||
// full, we may be able to write some arbitrary (but finite) number of
|
||||
// bytes to it without blocking.
|
||||
t.Logf("Wrote %d bytes into send buffer; retrying until buffer is full", n)
|
||||
if d <= maxDynamicTimeout/2 {
|
||||
// We don't know how long the actual write loop would have taken if
|
||||
// the buffer were full, so just guess and double the duration so that
|
||||
// the next attempt can make twice as much progress toward filling it.
|
||||
d *= 2
|
||||
}
|
||||
} else if next, ok := nextTimeout(actual); !ok {
|
||||
t.Fatalf("Write took %s; expected at most %s", actual, want)
|
||||
} else {
|
||||
// Maybe this machine is too slow to reliably schedule goroutines within
|
||||
// the requested duration. Increase the timeout and try again.
|
||||
t.Logf("Write took %s (expected %s); trying with longer timeout", actual, d)
|
||||
d = next
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
d := minDynamicTimeout
|
||||
for {
|
||||
t.Logf("SetWriteDeadline(+%v)", d)
|
||||
t0 := time.Now()
|
||||
deadline := t0.Add(d)
|
||||
if err := w.SetWriteDeadline(deadline); err != nil {
|
||||
t.Fatalf("SetWriteDeadline(%v): %v", deadline, err)
|
||||
}
|
||||
var n int64
|
||||
var err error
|
||||
for {
|
||||
var dn int
|
||||
dn, err = w.Write([]byte("TIMEOUT TRANSMITTER"))
|
||||
n += int64(dn)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
t1 := time.Now()
|
||||
// Inv: err != nil
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Fatalf("Write did not return (any, timeout): (%d, %v)", n, err)
|
||||
}
|
||||
|
||||
actual := t1.Sub(t0)
|
||||
if t1.Before(deadline) {
|
||||
t.Errorf("Write took %s; expected at least %s", actual, d)
|
||||
}
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
if want := timeoutUpperBound(d); actual > want {
|
||||
if n > 0 {
|
||||
// SetWriteDeadline specifies a time “after which I/O operations fail
|
||||
// instead of blocking”. However, the kernel's send buffer is not yet
|
||||
// full, we may be able to write some arbitrary (but finite) number of
|
||||
// bytes to it without blocking.
|
||||
t.Logf("Wrote %d bytes into send buffer; retrying until buffer is full", n)
|
||||
if d <= maxDynamicTimeout/2 {
|
||||
// We don't know how long the actual write loop would have taken if
|
||||
// the buffer were full, so just guess and double the duration so that
|
||||
// the next attempt can make twice as much progress toward filling it.
|
||||
d *= 2
|
||||
}
|
||||
} else if next, ok := nextTimeout(actual); !ok {
|
||||
t.Fatalf("Write took %s; expected at most %s", actual, want)
|
||||
} else {
|
||||
// Maybe this machine is too slow to reliably schedule goroutines within
|
||||
// the requested duration. Increase the timeout and try again.
|
||||
t.Logf("Write took %s (expected %s); trying with longer timeout", actual, d)
|
||||
d = next
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
func TestVariousDeadlines(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t)
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t, tc.create)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
@ -419,7 +447,12 @@ func TestVariousDeadlines1Proc(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
||||
testVariousDeadlines(t)
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t, tc.create)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
@ -429,7 +462,12 @@ func TestVariousDeadlines4Proc(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
||||
testVariousDeadlines(t)
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t, tc.create)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type neverEnding byte
|
||||
@ -441,7 +479,7 @@ func (b neverEnding) Read(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func testVariousDeadlines(t *testing.T) {
|
||||
func testVariousDeadlines(t *testing.T, create func(t *testing.T) (r, w *os.File)) {
|
||||
type result struct {
|
||||
n int64
|
||||
err error
|
||||
@ -487,10 +525,7 @@ func testVariousDeadlines(t *testing.T) {
|
||||
}
|
||||
for run := 0; run < numRuns; run++ {
|
||||
t.Run(fmt.Sprintf("%v-%d", timeout, run+1), func(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, w := create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -514,7 +549,7 @@ func testVariousDeadlines(t *testing.T) {
|
||||
|
||||
select {
|
||||
case res := <-actvch:
|
||||
if !isDeadlineExceeded(err) {
|
||||
if isDeadlineExceeded(res.err) {
|
||||
t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n)
|
||||
} else {
|
||||
t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err)
|
||||
@ -543,50 +578,53 @@ func TestReadWriteDeadlineRace(t *testing.T) {
|
||||
N = 50
|
||||
}
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
tic := time.NewTicker(2 * time.Microsecond)
|
||||
defer tic.Stop()
|
||||
for i := 0; i < N; i++ {
|
||||
if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
||||
break
|
||||
}
|
||||
if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
||||
break
|
||||
}
|
||||
<-tic.C
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var b [1]byte
|
||||
for i := 0; i < N; i++ {
|
||||
_, err := r.Read(b[:])
|
||||
if err != nil && !isDeadlineExceeded(err) {
|
||||
t.Error("Read returned non-timeout error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var b [1]byte
|
||||
for i := 0; i < N; i++ {
|
||||
_, err := w.Write(b[:])
|
||||
if err != nil && !isDeadlineExceeded(err) {
|
||||
t.Error("Write returned non-timeout error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait() // wait for tester goroutine to stop
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
tic := time.NewTicker(2 * time.Microsecond)
|
||||
defer tic.Stop()
|
||||
for i := 0; i < N; i++ {
|
||||
if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
||||
break
|
||||
}
|
||||
if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
||||
break
|
||||
}
|
||||
<-tic.C
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var b [1]byte
|
||||
for i := 0; i < N; i++ {
|
||||
_, err := r.Read(b[:])
|
||||
if err != nil && !isDeadlineExceeded(err) {
|
||||
t.Error("Read returned non-timeout error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var b [1]byte
|
||||
for i := 0; i < N; i++ {
|
||||
_, err := w.Write(b[:])
|
||||
if err != nil && !isDeadlineExceeded(err) {
|
||||
t.Error("Write returned non-timeout error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait() // wait for tester goroutine to stop
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRacyRead tests that it is safe to mutate the input Read buffer
|
||||
@ -594,37 +632,40 @@ func TestReadWriteDeadlineRace(t *testing.T) {
|
||||
func TestRacyRead(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
go io.Copy(w, rand.New(rand.NewSource(0)))
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
go io.Copy(w, rand.New(rand.NewSource(0)))
|
||||
|
||||
b1 := make([]byte, 1024)
|
||||
b2 := make([]byte, 1024)
|
||||
for j := 0; j < 100; j++ {
|
||||
_, err := r.Read(b1)
|
||||
copy(b1, b2) // Mutate b1 to trigger potential race
|
||||
if err != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Error(err)
|
||||
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
b1 := make([]byte, 1024)
|
||||
b2 := make([]byte, 1024)
|
||||
for j := 0; j < 100; j++ {
|
||||
_, err := r.Read(b1)
|
||||
copy(b1, b2) // Mutate b1 to trigger potential race
|
||||
if err != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||
}
|
||||
}
|
||||
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,73 +674,39 @@ func TestRacyRead(t *testing.T) {
|
||||
func TestRacyWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
go io.Copy(io.Discard, r)
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
go io.Copy(io.Discard, r)
|
||||
|
||||
b1 := make([]byte, 1024)
|
||||
b2 := make([]byte, 1024)
|
||||
for j := 0; j < 100; j++ {
|
||||
_, err := w.Write(b1)
|
||||
copy(b1, b2) // Mutate b1 to trigger potential race
|
||||
if err != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Error(err)
|
||||
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
b1 := make([]byte, 1024)
|
||||
b2 := make([]byte, 1024)
|
||||
for j := 0; j < 100; j++ {
|
||||
_, err := w.Write(b1)
|
||||
copy(b1, b2) // Mutate b1 to trigger potential race
|
||||
if err != nil {
|
||||
if !isDeadlineExceeded(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
||||
}
|
||||
}
|
||||
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Closing a TTY while reading from it should not hang. Issue 23943.
|
||||
func TestTTYClose(t *testing.T) {
|
||||
// Ignore SIGTTIN in case we are running in the background.
|
||||
signal.Ignore(syscall.SIGTTIN)
|
||||
defer signal.Reset(syscall.SIGTTIN)
|
||||
|
||||
f, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
t.Skipf("skipping because opening /dev/tty failed: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
var buf [1]byte
|
||||
f.Read(buf[:])
|
||||
}()
|
||||
|
||||
// Give the goroutine a chance to enter the read.
|
||||
// It doesn't matter much if it occasionally fails to do so,
|
||||
// we won't be testing what we want to test but the test will pass.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
defer close(c)
|
||||
f.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
case <-time.After(time.Second):
|
||||
t.Error("timed out waiting for close")
|
||||
}
|
||||
|
||||
// On some systems the goroutines may now be hanging.
|
||||
// There's not much we can do about that.
|
||||
}
|
||||
|
65
src/os/timeout_unix_test.go
Normal file
65
src/os/timeout_unix_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !js && !plan9 && !wasip1 && !windows
|
||||
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pipeDeadlinesTestCases = []pipeDeadlineTest{{
|
||||
"anonymous pipe",
|
||||
func(t *testing.T) (r, w *os.File) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return r, w
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// Closing a TTY while reading from it should not hang. Issue 23943.
|
||||
func TestTTYClose(t *testing.T) {
|
||||
// Ignore SIGTTIN in case we are running in the background.
|
||||
signal.Ignore(syscall.SIGTTIN)
|
||||
defer signal.Reset(syscall.SIGTTIN)
|
||||
|
||||
f, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
t.Skipf("skipping because opening /dev/tty failed: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
var buf [1]byte
|
||||
f.Read(buf[:])
|
||||
}()
|
||||
|
||||
// Give the goroutine a chance to enter the read.
|
||||
// It doesn't matter much if it occasionally fails to do so,
|
||||
// we won't be testing what we want to test but the test will pass.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
defer close(c)
|
||||
f.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
case <-time.After(time.Second):
|
||||
t.Error("timed out waiting for close")
|
||||
}
|
||||
|
||||
// On some systems the goroutines may now be hanging.
|
||||
// There's not much we can do about that.
|
||||
}
|
24
src/os/timeout_windows_test.go
Normal file
24
src/os/timeout_windows_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pipeDeadlinesTestCases = []pipeDeadlineTest{
|
||||
{
|
||||
"named overlapped pipe",
|
||||
func(t *testing.T) (r, w *os.File) {
|
||||
name := pipeName()
|
||||
w = newBytePipe(t, name, true)
|
||||
r = newFileOverlapped(t, name, true)
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user