| // Copyright 2020 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. |
| |
| //go:build !plan9 && !windows |
| // +build !plan9,!windows |
| |
| package main |
| |
| /* |
| #include <errno.h> |
| #include <signal.h> |
| #include <string.h> |
| |
| static int clearRestart(int sig) { |
| struct sigaction sa; |
| |
| memset(&sa, 0, sizeof sa); |
| if (sigaction(sig, NULL, &sa) < 0) { |
| return errno; |
| } |
| sa.sa_flags &=~ SA_RESTART; |
| if (sigaction(sig, &sa, NULL) < 0) { |
| return errno; |
| } |
| return 0; |
| } |
| */ |
| import "C" |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "io" |
| "log" |
| "net" |
| "os" |
| "os/exec" |
| "sync" |
| "syscall" |
| "time" |
| ) |
| |
| func init() { |
| register("EINTR", EINTR) |
| register("Block", Block) |
| } |
| |
| // Test various operations when a signal handler is installed without |
| // the SA_RESTART flag. This tests that the os and net APIs handle EINTR. |
| func EINTR() { |
| if errno := C.clearRestart(C.int(syscall.SIGURG)); errno != 0 { |
| log.Fatal(syscall.Errno(errno)) |
| } |
| if errno := C.clearRestart(C.int(syscall.SIGWINCH)); errno != 0 { |
| log.Fatal(syscall.Errno(errno)) |
| } |
| if errno := C.clearRestart(C.int(syscall.SIGCHLD)); errno != 0 { |
| log.Fatal(syscall.Errno(errno)) |
| } |
| |
| var wg sync.WaitGroup |
| testPipe(&wg) |
| testNet(&wg) |
| testExec(&wg) |
| wg.Wait() |
| fmt.Println("OK") |
| } |
| |
| // spin does CPU bound spinning and allocating for a millisecond, |
| // to get a SIGURG. |
| //go:noinline |
| func spin() (float64, []byte) { |
| stop := time.Now().Add(time.Millisecond) |
| r1 := 0.0 |
| r2 := make([]byte, 200) |
| for time.Now().Before(stop) { |
| for i := 1; i < 1e6; i++ { |
| r1 += r1 / float64(i) |
| r2 = append(r2, bytes.Repeat([]byte{byte(i)}, 100)...) |
| r2 = r2[100:] |
| } |
| } |
| return r1, r2 |
| } |
| |
| // winch sends a few SIGWINCH signals to the process. |
| func winch() { |
| ticker := time.NewTicker(100 * time.Microsecond) |
| defer ticker.Stop() |
| pid := syscall.Getpid() |
| for n := 10; n > 0; n-- { |
| syscall.Kill(pid, syscall.SIGWINCH) |
| <-ticker.C |
| } |
| } |
| |
| // sendSomeSignals triggers a few SIGURG and SIGWINCH signals. |
| func sendSomeSignals() { |
| done := make(chan struct{}) |
| go func() { |
| spin() |
| close(done) |
| }() |
| winch() |
| <-done |
| } |
| |
| // testPipe tests pipe operations. |
| func testPipe(wg *sync.WaitGroup) { |
| r, w, err := os.Pipe() |
| if err != nil { |
| log.Fatal(err) |
| } |
| if err := syscall.SetNonblock(int(r.Fd()), false); err != nil { |
| log.Fatal(err) |
| } |
| if err := syscall.SetNonblock(int(w.Fd()), false); err != nil { |
| log.Fatal(err) |
| } |
| wg.Add(2) |
| go func() { |
| defer wg.Done() |
| defer w.Close() |
| // Spin before calling Write so that the first ReadFull |
| // in the other goroutine will likely be interrupted |
| // by a signal. |
| 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 { |
| log.Fatal(err) |
| } |
| }() |
| go func() { |
| defer wg.Done() |
| defer r.Close() |
| 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 { |
| log.Fatal(err) |
| } |
| // Spin after reading half the data so that the Write |
| // in the other goroutine will likely be interrupted |
| // before it completes. |
| sendSomeSignals() |
| if _, err := io.ReadFull(r, b); err != nil { |
| log.Fatal(err) |
| } |
| }() |
| } |
| |
| // testNet tests network operations. |
| func testNet(wg *sync.WaitGroup) { |
| ln, err := net.Listen("tcp4", "127.0.0.1:0") |
| if err != nil { |
| if errors.Is(err, syscall.EAFNOSUPPORT) || errors.Is(err, syscall.EPROTONOSUPPORT) { |
| return |
| } |
| log.Fatal(err) |
| } |
| wg.Add(2) |
| go func() { |
| defer wg.Done() |
| defer ln.Close() |
| c, err := ln.Accept() |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer c.Close() |
| cf, err := c.(*net.TCPConn).File() |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer cf.Close() |
| if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil { |
| log.Fatal(err) |
| } |
| // See comments in testPipe. |
| sendSomeSignals() |
| if _, err := cf.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil { |
| log.Fatal(err) |
| } |
| }() |
| go func() { |
| defer wg.Done() |
| sendSomeSignals() |
| c, err := net.Dial("tcp", ln.Addr().String()) |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer c.Close() |
| cf, err := c.(*net.TCPConn).File() |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer cf.Close() |
| if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil { |
| log.Fatal(err) |
| } |
| // See comments in testPipe. |
| b := make([]byte, 1<<20) |
| if _, err := io.ReadFull(cf, b); err != nil { |
| log.Fatal(err) |
| } |
| sendSomeSignals() |
| if _, err := io.ReadFull(cf, b); err != nil { |
| log.Fatal(err) |
| } |
| }() |
| } |
| |
| func testExec(wg *sync.WaitGroup) { |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| cmd := exec.Command(os.Args[0], "Block") |
| stdin, err := cmd.StdinPipe() |
| if err != nil { |
| log.Fatal(err) |
| } |
| cmd.Stderr = new(bytes.Buffer) |
| cmd.Stdout = cmd.Stderr |
| if err := cmd.Start(); err != nil { |
| log.Fatal(err) |
| } |
| |
| go func() { |
| sendSomeSignals() |
| stdin.Close() |
| }() |
| |
| if err := cmd.Wait(); err != nil { |
| log.Fatalf("%v:\n%s", err, cmd.Stdout) |
| } |
| }() |
| } |
| |
| // Block blocks until stdin is closed. |
| func Block() { |
| io.Copy(io.Discard, os.Stdin) |
| } |