Source file src/runtime/semasleep_test.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build !plan9 && !windows && !js
     6  
     7  package runtime_test
     8  
     9  import (
    10  	"io"
    11  	"os/exec"
    12  	"syscall"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  // Issue #27250. Spurious wakeups to pthread_cond_timedwait_relative_np
    18  // shouldn't cause semasleep to retry with the same timeout which would
    19  // cause indefinite spinning.
    20  func TestSpuriousWakeupsNeverHangSemasleep(t *testing.T) {
    21  	if *flagQuick {
    22  		t.Skip("-quick")
    23  	}
    24  	t.Parallel() // Waits for a program to sleep for 1s.
    25  
    26  	exe, err := buildTestProg(t, "testprog")
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  
    31  	cmd := exec.Command(exe, "After1")
    32  	stdout, err := cmd.StdoutPipe()
    33  	if err != nil {
    34  		t.Fatalf("StdoutPipe: %v", err)
    35  	}
    36  	beforeStart := time.Now()
    37  	if err := cmd.Start(); err != nil {
    38  		t.Fatalf("Failed to start command: %v", err)
    39  	}
    40  	doneCh := make(chan error, 1)
    41  	go func() {
    42  		doneCh <- cmd.Wait()
    43  		close(doneCh)
    44  	}()
    45  	t.Cleanup(func() {
    46  		cmd.Process.Kill()
    47  		<-doneCh
    48  	})
    49  
    50  	// Wait for After1 to close its stdout so that we know the runtime's SIGIO
    51  	// handler is registered.
    52  	b, err := io.ReadAll(stdout)
    53  	if len(b) > 0 {
    54  		t.Logf("read from testprog stdout: %s", b)
    55  	}
    56  	if err != nil {
    57  		t.Fatalf("error reading from testprog: %v", err)
    58  	}
    59  
    60  	// Wait for an arbitrary timeout longer than one second. The subprocess itself
    61  	// attempts to sleep for one second, but if the machine running the test is
    62  	// heavily loaded that subprocess may not schedule very quickly even if the
    63  	// bug remains fixed. (This is fine, because if the bug really is unfixed we
    64  	// can keep the process hung indefinitely, as long as we signal it often
    65  	// enough.)
    66  	timeout := 10 * time.Second
    67  
    68  	// The subprocess begins sleeping for 1s after it writes to stdout, so measure
    69  	// the timeout from here (not from when we started creating the process).
    70  	// That should reduce noise from process startup overhead.
    71  	ready := time.Now()
    72  
    73  	// With the repro running, we can continuously send to it
    74  	// a signal that the runtime considers non-terminal,
    75  	// such as SIGIO, to spuriously wake up
    76  	// pthread_cond_timedwait_relative_np.
    77  	ticker := time.NewTicker(200 * time.Millisecond)
    78  	defer ticker.Stop()
    79  	for {
    80  		select {
    81  		case now := <-ticker.C:
    82  			if now.Sub(ready) > timeout {
    83  				t.Error("Program failed to return on time and has to be killed, issue #27520 still exists")
    84  				// Send SIGQUIT to get a goroutine dump.
    85  				// Stop sending SIGIO so that the program can clean up and actually terminate.
    86  				cmd.Process.Signal(syscall.SIGQUIT)
    87  				return
    88  			}
    89  
    90  			// Send the pesky signal that toggles spinning
    91  			// indefinitely if #27520 is not fixed.
    92  			cmd.Process.Signal(syscall.SIGIO)
    93  
    94  		case err := <-doneCh:
    95  			if err != nil {
    96  				t.Fatalf("The program returned but unfortunately with an error: %v", err)
    97  			}
    98  			if time.Since(beforeStart) < 1*time.Second {
    99  				// The program was supposed to sleep for a full (monotonic) second;
   100  				// it should not return before that has elapsed.
   101  				t.Fatalf("The program stopped too quickly.")
   102  			}
   103  			return
   104  		}
   105  	}
   106  }
   107  

View as plain text