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>
diff --git a/src/runtime/testdata/testprogcgo/eintr.go b/src/runtime/testdata/testprogcgo/eintr.go
index cd88c15..58f0dd2 100644
--- a/src/runtime/testdata/testprogcgo/eintr.go
+++ b/src/runtime/testdata/testprogcgo/eintr.go
@@ -36,6 +36,7 @@
 	"net"
 	"os"
 	"os/exec"
+	"os/signal"
 	"sync"
 	"syscall"
 	"time"
@@ -43,7 +44,7 @@
 
 func init() {
 	register("EINTR", EINTR)
-	register("Nop", Nop)
+	register("Block", Block)
 }
 
 // Test various operations when a signal handler is installed without
@@ -59,13 +60,6 @@
 		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
 	testPipe(&wg)
 	testNet(&wg)
@@ -90,6 +84,22 @@
 	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.
 func testPipe(wg *sync.WaitGroup) {
 	r, w, err := os.Pipe()
@@ -109,19 +119,19 @@
 		// Spin before calling Write so that the first ReadFull
 		// in the other goroutine will likely be interrupted
 		// by a signal.
-		spin()
+		sendSomeSignals()
 		// This Write will likely be interrupted by a signal
 		// as the other goroutine spins in the middle of reading.
 		// We write enough data that we should always fill the
 		// 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)
 		}
 	}()
 	go func() {
 		defer wg.Done()
 		defer r.Close()
-		b := make([]byte, 1 << 20)
+		b := make([]byte, 1<<20)
 		// This ReadFull will likely be interrupted by a signal,
 		// as the other goroutine spins before writing anything.
 		if _, err := io.ReadFull(r, b); err != nil {
@@ -130,7 +140,7 @@
 		// Spin after reading half the data so that the Write
 		// in the other goroutine will likely be interrupted
 		// before it completes.
-		spin()
+		sendSomeSignals()
 		if _, err := io.ReadFull(r, b); err != nil {
 			log.Fatal(err)
 		}
@@ -164,14 +174,14 @@
 			log.Fatal(err)
 		}
 		// See comments in testPipe.
-		spin()
-		if _, err := cf.Write(bytes.Repeat([]byte{0}, 2 << 20)); err != nil {
+		sendSomeSignals()
+		if _, err := cf.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
 			log.Fatal(err)
 		}
 	}()
 	go func() {
 		defer wg.Done()
-		spin()
+		sendSomeSignals()
 		c, err := net.Dial("tcp", ln.Addr().String())
 		if err != nil {
 			log.Fatal(err)
@@ -186,11 +196,11 @@
 			log.Fatal(err)
 		}
 		// See comments in testPipe.
-		b := make([]byte, 1 << 20)
+		b := make([]byte, 1<<20)
 		if _, err := io.ReadFull(cf, b); err != nil {
 			log.Fatal(err)
 		}
-		spin()
+		sendSomeSignals()
 		if _, err := io.ReadFull(cf, b); err != nil {
 			log.Fatal(err)
 		}
@@ -201,14 +211,30 @@
 	wg.Add(1)
 	go func() {
 		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)
 		}
+
+		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
-// for a child.
-func Nop() {
-	time.Sleep(time.Millisecond)
+// Block blocks until the process receives os.Interrupt.
+func Block() {
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt)
+	defer signal.Stop(c)
+	<-c
 }