Skip to content

Commit f2884bf

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
os: deflake TestPipeEOF and TestFifoEOF
- Consolidate the two test bodies as one helper function. - Eliminate arbitrary timeout. - Shorten arbitrary sleeps in short mode. - Simplify goroutines. - Mark the tests as parallel. Fixes #36107. Updates #24164. Change-Id: I14fe4395963a7256cb6d2d743d348a1ade077d5b Reviewed-on: https://go-review.googlesource.com/c/go/+/457336 Reviewed-by: Rob Pike <[email protected]> Run-TryBot: Bryan Mills <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Bryan Mills <[email protected]>
1 parent 3bb8864 commit f2884bf

File tree

2 files changed

+75
-113
lines changed

2 files changed

+75
-113
lines changed

src/os/fifo_test.go

+27-69
Original file line numberDiff line numberDiff line change
@@ -2,102 +2,60 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd
5+
//go:build darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd
66

77
package os_test
88

99
import (
10-
"bufio"
11-
"bytes"
12-
"fmt"
13-
"io"
1410
"os"
1511
"path/filepath"
16-
"runtime"
17-
"sync"
1812
"syscall"
1913
"testing"
20-
"time"
2114
)
2215

23-
// Issue 24164.
2416
func TestFifoEOF(t *testing.T) {
25-
switch runtime.GOOS {
26-
case "android":
27-
t.Skip("skipping on Android; mkfifo syscall not available")
28-
}
17+
t.Parallel()
2918

3019
dir := t.TempDir()
3120
fifoName := filepath.Join(dir, "fifo")
3221
if err := syscall.Mkfifo(fifoName, 0600); err != nil {
3322
t.Fatal(err)
3423
}
3524

36-
var wg sync.WaitGroup
37-
wg.Add(1)
25+
// Per https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html#tag_16_357_03:
26+
//
27+
// - “If O_NONBLOCK is clear, an open() for reading-only shall block the
28+
// calling thread until a thread opens the file for writing. An open() for
29+
// writing-only shall block the calling thread until a thread opens the file
30+
// for reading.”
31+
//
32+
// In order to unblock both open calls, we open the two ends of the FIFO
33+
// simultaneously in separate goroutines.
34+
35+
rc := make(chan *os.File, 1)
3836
go func() {
39-
defer wg.Done()
40-
41-
w, err := os.OpenFile(fifoName, os.O_WRONLY, 0)
37+
r, err := os.Open(fifoName)
4238
if err != nil {
4339
t.Error(err)
44-
return
4540
}
46-
47-
defer func() {
48-
if err := w.Close(); err != nil {
49-
t.Errorf("error closing writer: %v", err)
50-
}
51-
}()
52-
53-
for i := 0; i < 3; i++ {
54-
time.Sleep(10 * time.Millisecond)
55-
_, err := fmt.Fprintf(w, "line %d\n", i)
56-
if err != nil {
57-
t.Errorf("error writing to fifo: %v", err)
58-
return
59-
}
60-
}
61-
time.Sleep(10 * time.Millisecond)
41+
rc <- r
6242
}()
6343

64-
defer wg.Wait()
65-
66-
r, err := os.Open(fifoName)
44+
w, err := os.OpenFile(fifoName, os.O_WRONLY, 0)
6745
if err != nil {
68-
t.Fatal(err)
46+
t.Error(err)
6947
}
7048

71-
done := make(chan bool)
72-
go func() {
73-
defer close(done)
74-
75-
defer func() {
76-
if err := r.Close(); err != nil {
77-
t.Errorf("error closing reader: %v", err)
78-
}
79-
}()
80-
81-
rbuf := bufio.NewReader(r)
82-
for {
83-
b, err := rbuf.ReadBytes('\n')
84-
if err == io.EOF {
85-
break
86-
}
87-
if err != nil {
88-
t.Error(err)
89-
return
90-
}
91-
t.Logf("%s\n", bytes.TrimSpace(b))
49+
r := <-rc
50+
if t.Failed() {
51+
if r != nil {
52+
r.Close()
9253
}
93-
}()
94-
95-
select {
96-
case <-done:
97-
// Test succeeded.
98-
case <-time.After(time.Second):
99-
t.Error("timed out waiting for read")
100-
// Close the reader to force the read to complete.
101-
r.Close()
54+
if w != nil {
55+
w.Close()
56+
}
57+
return
10258
}
59+
60+
testPipeEOF(t, r, w)
10361
}

src/os/pipe_test.go

+48-44
Original file line numberDiff line numberDiff line change
@@ -339,68 +339,72 @@ func testCloseWithBlockingRead(t *testing.T, r, w *os.File) {
339339
wg.Wait()
340340
}
341341

342-
// Issue 24164, for pipes.
343342
func TestPipeEOF(t *testing.T) {
343+
t.Parallel()
344+
344345
r, w, err := os.Pipe()
345346
if err != nil {
346347
t.Fatal(err)
347348
}
348349

349-
var wg sync.WaitGroup
350-
wg.Add(1)
351-
go func() {
352-
defer wg.Done()
353-
354-
defer func() {
355-
if err := w.Close(); err != nil {
356-
t.Errorf("error closing writer: %v", err)
357-
}
358-
}()
350+
testPipeEOF(t, r, w)
351+
}
359352

360-
for i := 0; i < 3; i++ {
361-
time.Sleep(10 * time.Millisecond)
362-
_, err := fmt.Fprintf(w, "line %d\n", i)
363-
if err != nil {
364-
t.Errorf("error writing to fifo: %v", err)
365-
return
366-
}
353+
// testPipeEOF tests that when the write side of a pipe or FIFO is closed,
354+
// a blocked Read call on the reader side returns io.EOF.
355+
//
356+
// This scenario previously failed to unblock the Read call on darwin.
357+
// (See https://go.dev/issue/24164.)
358+
func testPipeEOF(t *testing.T, r io.ReadCloser, w io.WriteCloser) {
359+
// parkDelay is an arbitrary delay we wait for a pipe-reader goroutine to park
360+
// before issuing the corresponding write. The test should pass no matter what
361+
// delay we use, but with a longer delay is has a higher chance of detecting
362+
// poller bugs.
363+
parkDelay := 10 * time.Millisecond
364+
if testing.Short() {
365+
parkDelay = 100 * time.Microsecond
366+
}
367+
writerDone := make(chan struct{})
368+
defer func() {
369+
if err := r.Close(); err != nil {
370+
t.Errorf("error closing reader: %v", err)
367371
}
368-
time.Sleep(10 * time.Millisecond)
372+
<-writerDone
369373
}()
370374

371-
defer wg.Wait()
372-
373-
done := make(chan bool)
375+
write := make(chan int, 1)
374376
go func() {
375-
defer close(done)
376-
377-
defer func() {
378-
if err := r.Close(); err != nil {
379-
t.Errorf("error closing reader: %v", err)
380-
}
381-
}()
377+
defer close(writerDone)
382378

383-
rbuf := bufio.NewReader(r)
384-
for {
385-
b, err := rbuf.ReadBytes('\n')
386-
if err == io.EOF {
387-
break
388-
}
379+
for i := range write {
380+
time.Sleep(parkDelay)
381+
_, err := fmt.Fprintf(w, "line %d\n", i)
389382
if err != nil {
390-
t.Error(err)
383+
t.Errorf("error writing to fifo: %v", err)
391384
return
392385
}
393-
t.Logf("%s\n", bytes.TrimSpace(b))
386+
}
387+
388+
time.Sleep(parkDelay)
389+
if err := w.Close(); err != nil {
390+
t.Errorf("error closing writer: %v", err)
394391
}
395392
}()
396393

397-
select {
398-
case <-done:
399-
// Test succeeded.
400-
case <-time.After(time.Second):
401-
t.Error("timed out waiting for read")
402-
// Close the reader to force the read to complete.
403-
r.Close()
394+
rbuf := bufio.NewReader(r)
395+
for i := 0; i < 3; i++ {
396+
write <- i
397+
b, err := rbuf.ReadBytes('\n')
398+
if err != nil {
399+
t.Fatal(err)
400+
}
401+
t.Logf("%s\n", bytes.TrimSpace(b))
402+
}
403+
404+
close(write)
405+
b, err := rbuf.ReadBytes('\n')
406+
if err != io.EOF || len(b) != 0 {
407+
t.Errorf(`ReadBytes: %q, %v; want "", io.EOF`, b, err)
404408
}
405409
}
406410

0 commit comments

Comments
 (0)