runtime: reduce timing sensitivity in TestEINTR

- Don't assume that a process interrupted at 100μs intervals will have
  enough remaining time to make progress. (Stop sending signals
  in between signal storms to allow the process to quiesce.)

- Don't assume that a child process that spins for 1ms will block long
  enough for the parent process to receive signals or make meaningful
  progress. (Instead, have the child block indefinitely, and unblock
  it explicitly after the signal storm.)

For #39043
Updates #22838
Updates #20400

Change-Id: I85cba23498c346a637e6cfe8684ca0c478562a93
Reviewed-on: https://go-review.googlesource.com/c/go/+/233877
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Bryan C. Mills 2020-05-13 12:43:46 -04:00
parent 810c27e9be
commit ee0d40cba4

View File

@ -36,6 +36,7 @@ import (
"net" "net"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@ -43,7 +44,7 @@ import (
func init() { func init() {
register("EINTR", EINTR) register("EINTR", EINTR)
register("Nop", Nop) register("Block", Block)
} }
// Test various operations when a signal handler is installed without // Test various operations when a signal handler is installed without
@ -59,13 +60,6 @@ func EINTR() {
log.Fatal(syscall.Errno(errno)) log.Fatal(syscall.Errno(errno))
} }
// Send ourselves SIGWINCH regularly.
go func() {
for range time.Tick(100 * time.Microsecond) {
syscall.Kill(0, syscall.SIGWINCH)
}
}()
var wg sync.WaitGroup var wg sync.WaitGroup
testPipe(&wg) testPipe(&wg)
testNet(&wg) testNet(&wg)
@ -90,6 +84,22 @@ func spin() (float64, [][]byte) {
return r1, r2 return r1, r2
} }
// winch sends a few SIGWINCH signals to the process.
func winch() {
ticker := time.NewTicker(100 * time.Microsecond)
defer ticker.Stop()
for n := 10; n > 0; n-- {
syscall.Kill(0, syscall.SIGWINCH)
<-ticker.C
}
}
// sendSomeSignals triggers a few SIGURG and SIGWINCH signals.
func sendSomeSignals() {
spin()
winch()
}
// testPipe tests pipe operations. // testPipe tests pipe operations.
func testPipe(wg *sync.WaitGroup) { func testPipe(wg *sync.WaitGroup) {
r, w, err := os.Pipe() r, w, err := os.Pipe()
@ -109,19 +119,19 @@ func testPipe(wg *sync.WaitGroup) {
// Spin before calling Write so that the first ReadFull // Spin before calling Write so that the first ReadFull
// in the other goroutine will likely be interrupted // in the other goroutine will likely be interrupted
// by a signal. // by a signal.
spin() sendSomeSignals()
// This Write will likely be interrupted by a signal // This Write will likely be interrupted by a signal
// as the other goroutine spins in the middle of reading. // as the other goroutine spins in the middle of reading.
// We write enough data that we should always fill the // We write enough data that we should always fill the
// pipe buffer and need multiple write system calls. // pipe buffer and need multiple write system calls.
if _, err := w.Write(bytes.Repeat([]byte{0}, 2 << 20)); err != nil { if _, err := w.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
log.Fatal(err) log.Fatal(err)
} }
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
defer r.Close() defer r.Close()
b := make([]byte, 1 << 20) b := make([]byte, 1<<20)
// This ReadFull will likely be interrupted by a signal, // This ReadFull will likely be interrupted by a signal,
// as the other goroutine spins before writing anything. // as the other goroutine spins before writing anything.
if _, err := io.ReadFull(r, b); err != nil { if _, err := io.ReadFull(r, b); err != nil {
@ -130,7 +140,7 @@ func testPipe(wg *sync.WaitGroup) {
// Spin after reading half the data so that the Write // Spin after reading half the data so that the Write
// in the other goroutine will likely be interrupted // in the other goroutine will likely be interrupted
// before it completes. // before it completes.
spin() sendSomeSignals()
if _, err := io.ReadFull(r, b); err != nil { if _, err := io.ReadFull(r, b); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -164,14 +174,14 @@ func testNet(wg *sync.WaitGroup) {
log.Fatal(err) log.Fatal(err)
} }
// See comments in testPipe. // See comments in testPipe.
spin() sendSomeSignals()
if _, err := cf.Write(bytes.Repeat([]byte{0}, 2 << 20)); err != nil { if _, err := cf.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
log.Fatal(err) log.Fatal(err)
} }
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
spin() sendSomeSignals()
c, err := net.Dial("tcp", ln.Addr().String()) c, err := net.Dial("tcp", ln.Addr().String())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -186,11 +196,11 @@ func testNet(wg *sync.WaitGroup) {
log.Fatal(err) log.Fatal(err)
} }
// See comments in testPipe. // See comments in testPipe.
b := make([]byte, 1 << 20) b := make([]byte, 1<<20)
if _, err := io.ReadFull(cf, b); err != nil { if _, err := io.ReadFull(cf, b); err != nil {
log.Fatal(err) log.Fatal(err)
} }
spin() sendSomeSignals()
if _, err := io.ReadFull(cf, b); err != nil { if _, err := io.ReadFull(cf, b); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -201,14 +211,30 @@ func testExec(wg *sync.WaitGroup) {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := exec.Command(os.Args[0], "Nop").Run(); err != nil { cmd := exec.Command(os.Args[0], "Block")
cmd.Stderr = new(bytes.Buffer)
cmd.Stdout = cmd.Stderr
if err := cmd.Start(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
go func() {
sendSomeSignals()
if err := cmd.Process.Signal(os.Interrupt); err != nil {
panic(err)
}
}()
if err := cmd.Wait(); err != nil {
log.Fatalf("%v:\n%s", err, cmd.Stdout)
}
}() }()
} }
// Nop just sleeps for a bit. This is used to test interrupts while waiting // Block blocks until the process receives os.Interrupt.
// for a child. func Block() {
func Nop() { c := make(chan os.Signal, 1)
time.Sleep(time.Millisecond) signal.Notify(c, os.Interrupt)
defer signal.Stop(c)
<-c
} }