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:
qmuntal 2025-04-25 10:12:28 +02:00 committed by Quim Muntal
parent 06751c455d
commit 3cebfb678b
3 changed files with 463 additions and 367 deletions

View File

@ -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.
}

View 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.
}

View 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
},
},
}
}