misc/cgo/testcarchive: more robust TestSignalForwardingExternal
Try to avoid a race condition in the test. Passed 500 times on my
laptop.
Fixes #14956.
Change-Id: I5de2e1e3623832f0ab4f180149f7c57ce7cd23c0
Reviewed-on: https://go-review.googlesource.com/21171
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/misc/cgo/testcarchive/carchive_test.go b/misc/cgo/testcarchive/carchive_test.go
index 2c81a6a..97e0c78 100644
--- a/misc/cgo/testcarchive/carchive_test.go
+++ b/misc/cgo/testcarchive/carchive_test.go
@@ -14,6 +14,7 @@
"strings"
"syscall"
"testing"
+ "time"
"unicode"
)
@@ -312,44 +313,65 @@
t.Fatal(err)
}
- cmd = exec.Command(bin[0], append(bin[1:], "2")...)
+ // We want to send the process a signal and see if it dies.
+ // Normally the signal goes to the C thread, the Go signal
+ // handler picks it up, sees that it is running in a C thread,
+ // and the program dies. Unfortunately, occasionally the
+ // signal is delivered to a Go thread, which winds up
+ // discarding it because it was sent by another program and
+ // there is no Go handler for it. To avoid this, run the
+ // program several times in the hopes that it will eventually
+ // fail.
+ const tries = 20
+ for i := 0; i < tries; i++ {
+ cmd = exec.Command(bin[0], append(bin[1:], "2")...)
- stderr, err := cmd.StderrPipe()
- if err != nil {
- t.Fatal(err)
- }
- defer stderr.Close()
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer stderr.Close()
- r := bufio.NewReader(stderr)
+ r := bufio.NewReader(stderr)
- err = cmd.Start()
+ err = cmd.Start()
- if err != nil {
- t.Fatal(err)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Wait for trigger to ensure that the process is started.
+ ok, err := r.ReadString('\n')
+
+ // Verify trigger.
+ if err != nil || ok != "OK\n" {
+ t.Fatalf("Did not receive OK signal")
+ }
+
+ // Give the program a chance to enter the sleep function.
+ time.Sleep(time.Millisecond)
+
+ cmd.Process.Signal(syscall.SIGSEGV)
+
+ err = cmd.Wait()
+
+ if err == nil {
+ continue
+ }
+
+ if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
+ } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
+ t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
+ } else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
+ t.Errorf("got %v; expected SIGSEGV", ee)
+ } else {
+ // We got the error we expected.
+ return
+ }
}
- // Wait for trigger to ensure that the process is started.
- ok, err := r.ReadString('\n')
-
- // Verify trigger.
- if err != nil || ok != "OK\n" {
- t.Fatalf("Did not receive OK signal")
- }
-
- // Trigger an interrupt external to the process.
- cmd.Process.Signal(syscall.SIGSEGV)
-
- err = cmd.Wait()
-
- if err == nil {
- t.Error("test program succeeded unexpectedly")
- } else if ee, ok := err.(*exec.ExitError); !ok {
- t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
- } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
- t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
- } else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
- t.Errorf("got %v; expected SIGSEGV", ee)
- }
+ t.Errorf("program succeeded unexpectedly %d times", tries)
}
func TestOsSignal(t *testing.T) {
diff --git a/misc/cgo/testcarchive/main5.c b/misc/cgo/testcarchive/main5.c
index 556abdf..9fadf08 100644
--- a/misc/cgo/testcarchive/main5.c
+++ b/misc/cgo/testcarchive/main5.c
@@ -8,6 +8,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/select.h>
#include "libgo2.h"
@@ -16,7 +19,7 @@
int test;
if (argc < 2) {
- printf("Missing argument");
+ printf("Missing argument\n");
return 1;
}
@@ -28,7 +31,7 @@
printf("calling RunGoroutines\n");
}
- RunGoroutines();
+ Noop();
switch (test) {
case 1: {
@@ -41,6 +44,8 @@
}
case 2: {
+ struct timeval tv;
+
if (verbose) {
printf("attempting external signal test\n");
}
@@ -48,8 +53,18 @@
fprintf(stderr, "OK\n");
fflush(stderr);
- // The program should be interrupted before this sleep finishes.
- sleep(60);
+ // The program should be interrupted before
+ // this sleep finishes. We use select rather
+ // than sleep because in older versions of
+ // glibc the sleep function does some signal
+ // fiddling to handle SIGCHLD. If this
+ // program is fiddling signals just when the
+ // test program sends the signal, the signal
+ // may be delivered to a Go thread which will
+ // break this test.
+ tv.tv_sec = 60;
+ tv.tv_usec = 0;
+ select(0, NULL, NULL, NULL, &tv);
break;
}
diff --git a/misc/cgo/testcarchive/src/libgo2/libgo2.go b/misc/cgo/testcarchive/src/libgo2/libgo2.go
index ab40b75..fbed493 100644
--- a/misc/cgo/testcarchive/src/libgo2/libgo2.go
+++ b/misc/cgo/testcarchive/src/libgo2/libgo2.go
@@ -41,5 +41,10 @@
os.Exit(1)
}
+// Noop ensures that the Go runtime is initialized.
+//export Noop
+func Noop() {
+}
+
func main() {
}