blob: 791ff1bedc03475bf13e9fb8af3c268fb5abadd8 [file] [log] [blame]
// 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.
// +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"
"io/ioutil"
"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(ioutil.Discard, os.Stdin)
}