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 && !wasip1
     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  
    41  	waiting := false
    42  	doneCh := make(chan error, 1)
    43  	t.Cleanup(func() {
    44  		cmd.Process.Kill()
    45  		if waiting {
    46  			<-doneCh
    47  		} else {
    48  			cmd.Wait()
    49  		}
    50  	})
    51  
    52  	// Wait for After1 to close its stdout so that we know the runtime's SIGIO
    53  	// handler is registered.
    54  	b, err := io.ReadAll(stdout)
    55  	if len(b) > 0 {
    56  		t.Logf("read from testprog stdout: %s", b)
    57  	}
    58  	if err != nil {
    59  		t.Fatalf("error reading from testprog: %v", err)
    60  	}
    61  
    62  	// Wait for child exit.
    63  	//
    64  	// Note that we must do this after waiting for the write/child end of
    65  	// stdout to close. Wait closes the read/parent end of stdout, so
    66  	// starting this goroutine prior to io.ReadAll introduces a race
    67  	// condition where ReadAll may get fs.ErrClosed if the child exits too
    68  	// quickly.
    69  	waiting = true
    70  	go func() {
    71  		doneCh <- cmd.Wait()
    72  		close(doneCh)
    73  	}()
    74  
    75  	// Wait for an arbitrary timeout longer than one second. The subprocess itself
    76  	// attempts to sleep for one second, but if the machine running the test is
    77  	// heavily loaded that subprocess may not schedule very quickly even if the
    78  	// bug remains fixed. (This is fine, because if the bug really is unfixed we
    79  	// can keep the process hung indefinitely, as long as we signal it often
    80  	// enough.)
    81  	timeout := 10 * time.Second
    82  
    83  	// The subprocess begins sleeping for 1s after it writes to stdout, so measure
    84  	// the timeout from here (not from when we started creating the process).
    85  	// That should reduce noise from process startup overhead.
    86  	ready := time.Now()
    87  
    88  	// With the repro running, we can continuously send to it
    89  	// a signal that the runtime considers non-terminal,
    90  	// such as SIGIO, to spuriously wake up
    91  	// pthread_cond_timedwait_relative_np.
    92  	ticker := time.NewTicker(200 * time.Millisecond)
    93  	defer ticker.Stop()
    94  	for {
    95  		select {
    96  		case now := <-ticker.C:
    97  			if now.Sub(ready) > timeout {
    98  				t.Error("Program failed to return on time and has to be killed, issue #27520 still exists")
    99  				// Send SIGQUIT to get a goroutine dump.
   100  				// Stop sending SIGIO so that the program can clean up and actually terminate.
   101  				cmd.Process.Signal(syscall.SIGQUIT)
   102  				return
   103  			}
   104  
   105  			// Send the pesky signal that toggles spinning
   106  			// indefinitely if #27520 is not fixed.
   107  			cmd.Process.Signal(syscall.SIGIO)
   108  
   109  		case err := <-doneCh:
   110  			if err != nil {
   111  				t.Fatalf("The program returned but unfortunately with an error: %v", err)
   112  			}
   113  			if time.Since(beforeStart) < 1*time.Second {
   114  				// The program was supposed to sleep for a full (monotonic) second;
   115  				// it should not return before that has elapsed.
   116  				t.Fatalf("The program stopped too quickly.")
   117  			}
   118  			return
   119  		}
   120  	}
   121  }
   122  

View as plain text