runtime: regression test for semasleep indefinite hang

A regression test in which: for a program that invokes semasleep,
we send non-terminal signals such as SIGIO.
Since the signal wakes up pthread_cond_timedwait_relative_np,
after CL 133655, we should only re-spin for the amount of
time left, instead of re-spinning with the original duration
which would cause an indefinite spin.

Updates #27520

Change-Id: I744a6d04cf8923bc4e13649446aff5e42b7de5d8
Reviewed-on: https://go-review.googlesource.com/135015
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
diff --git a/src/runtime/semasleep_test.go b/src/runtime/semasleep_test.go
new file mode 100644
index 0000000..4a8b4db
--- /dev/null
+++ b/src/runtime/semasleep_test.go
@@ -0,0 +1,88 @@
+// Copyright 2018 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.
+
+//+build !nacl,!windows,!js
+
+package runtime_test
+
+import (
+	"internal/testenv"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"syscall"
+	"testing"
+	"time"
+)
+
+// Issue #27250. Spurious wakeups to pthread_cond_timedwait_relative_np
+// shouldn't cause semasleep to retry with the same timeout which would
+// cause indefinite spinning.
+func TestSpuriousWakeupsNeverHangSemasleep(t *testing.T) {
+	testenv.MustHaveGoBuild(t)
+	tempDir, err := ioutil.TempDir("", "issue-27250")
+	if err != nil {
+		t.Fatalf("Failed to create the temp directory: %v", err)
+	}
+	defer os.RemoveAll(tempDir)
+
+	repro := `
+    package main
+
+    import "time"
+
+    func main() {
+        <-time.After(1 * time.Second)
+    }
+    `
+	mainPath := filepath.Join(tempDir, "main.go")
+	if err := ioutil.WriteFile(mainPath, []byte(repro), 0644); err != nil {
+		t.Fatalf("Failed to create temp file for repro.go: %v", err)
+	}
+	binaryPath := filepath.Join(tempDir, "binary")
+
+	// Build the binary so that we can send the signal to its PID.
+	out, err := exec.Command(testenv.GoToolPath(t), "build", "-o", binaryPath, mainPath).CombinedOutput()
+	if err != nil {
+		t.Fatalf("Failed to compile the binary: err: %v\nOutput: %s\n", err, out)
+	}
+	if err := os.Chmod(binaryPath, 0755); err != nil {
+		t.Fatalf("Failed to chmod binary: %v", err)
+	}
+
+	// Now run the binary.
+	cmd := exec.Command(binaryPath)
+	if err := cmd.Start(); err != nil {
+		t.Fatalf("Failed to start command: %v", err)
+	}
+	doneCh := make(chan error, 1)
+	go func() {
+		doneCh <- cmd.Wait()
+	}()
+
+	// With the repro running, we can continuously send to it
+	// a non-terminal signal such as SIGIO, to spuriously
+	// wakeup pthread_cond_timedwait_relative_np.
+	unfixedTimer := time.NewTimer(2 * time.Second)
+	for {
+		select {
+		case <-time.After(200 * time.Millisecond):
+			// Send the pesky signal that toggles spinning
+			// indefinitely if #27520 is not fixed.
+			cmd.Process.Signal(syscall.SIGIO)
+
+		case <-unfixedTimer.C:
+			t.Error("Program failed to return on time and has to be killed, issue #27520 still exists")
+			cmd.Process.Signal(syscall.SIGKILL)
+			return
+
+		case err := <-doneCh:
+			if err != nil {
+				t.Fatalf("The program returned but unfortunately with an error: %v", err)
+			}
+			return
+		}
+	}
+}