624 lines
13 KiB
Go
624 lines
13 KiB
Go
// Copyright 2017 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 !js && !plan9 && !windows
|
|
|
|
package os_test
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNonpollableDeadline(t *testing.T) {
|
|
// On BSD systems regular files seem to be pollable,
|
|
// so just run this test on Linux.
|
|
if runtime.GOOS != "linux" {
|
|
t.Skipf("skipping on %s", runtime.GOOS)
|
|
}
|
|
|
|
f, err := os.CreateTemp("", "ostest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(f.Name())
|
|
defer f.Close()
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
if err := f.SetDeadline(deadline); err != os.ErrNoDeadline {
|
|
t.Errorf("SetDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
|
|
}
|
|
if err := f.SetReadDeadline(deadline); err != os.ErrNoDeadline {
|
|
t.Errorf("SetReadDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
|
|
}
|
|
if err := f.SetWriteDeadline(deadline); err != os.ErrNoDeadline {
|
|
t.Errorf("SetWriteDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
|
|
}
|
|
}
|
|
|
|
// noDeadline is a zero time.Time value, which cancels a deadline.
|
|
var noDeadline time.Time
|
|
|
|
var readTimeoutTests = []struct {
|
|
timeout time.Duration
|
|
xerrs [2]error // expected errors in transition
|
|
}{
|
|
// Tests that read deadlines work, even if there's data ready
|
|
// to be read.
|
|
{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
|
|
|
|
{50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
|
|
}
|
|
|
|
func TestReadTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, tt := range readTimeoutTests {
|
|
if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil {
|
|
t.Fatalf("#%d: %v", i, err)
|
|
}
|
|
var b [1]byte
|
|
for j, xerr := range tt.xerrs {
|
|
for {
|
|
n, err := r.Read(b[:])
|
|
if xerr != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatalf("#%d/%d: %v", i, j, err)
|
|
}
|
|
}
|
|
if err == nil {
|
|
time.Sleep(tt.timeout / 3)
|
|
continue
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("#%d/%d: read %d; want 0", i, j, n)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadTimeoutMustNotReturn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
max := time.NewTimer(100 * time.Millisecond)
|
|
defer max.Stop()
|
|
ch := make(chan error)
|
|
go func() {
|
|
if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := r.SetReadDeadline(noDeadline); err != nil {
|
|
t.Error(err)
|
|
}
|
|
var b [1]byte
|
|
_, err := r.Read(b[:])
|
|
ch <- err
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
t.Fatalf("expected Read to not return, but it returned with %v", err)
|
|
case <-max.C:
|
|
w.Close()
|
|
err := <-ch // wait for tester goroutine to stop
|
|
if os.IsTimeout(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var writeTimeoutTests = []struct {
|
|
timeout time.Duration
|
|
xerrs [2]error // expected errors in transition
|
|
}{
|
|
// Tests that write deadlines work, even if there's buffer
|
|
// space available to write.
|
|
{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
|
|
|
|
{10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
|
|
}
|
|
|
|
func TestWriteTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for i, tt := range writeTimeoutTests {
|
|
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
for j, xerr := range tt.xerrs {
|
|
for {
|
|
n, err := w.Write([]byte("WRITE TIMEOUT TEST"))
|
|
if xerr != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatalf("%d: %v", j, err)
|
|
}
|
|
}
|
|
if err == nil {
|
|
time.Sleep(tt.timeout / 3)
|
|
continue
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("%d: wrote %d; want 0", j, n)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWriteTimeoutMustNotReturn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
max := time.NewTimer(100 * time.Millisecond)
|
|
defer max.Stop()
|
|
ch := make(chan error)
|
|
go func() {
|
|
if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := w.SetWriteDeadline(noDeadline); err != nil {
|
|
t.Error(err)
|
|
}
|
|
var b [1]byte
|
|
for {
|
|
if _, err := w.Write(b[:]); err != nil {
|
|
ch <- err
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
t.Fatalf("expected Write to not return, but it returned with %v", err)
|
|
case <-max.C:
|
|
r.Close()
|
|
err := <-ch // wait for tester goroutine to stop
|
|
if os.IsTimeout(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func timeoutReader(r *os.File, d, min, max time.Duration, ch chan<- error) {
|
|
var err error
|
|
defer func() { ch <- err }()
|
|
|
|
t0 := time.Now()
|
|
if err = r.SetReadDeadline(time.Now().Add(d)); err != nil {
|
|
return
|
|
}
|
|
b := make([]byte, 256)
|
|
var n int
|
|
n, err = r.Read(b)
|
|
t1 := time.Now()
|
|
if n != 0 || err == nil || !isDeadlineExceeded(err) {
|
|
err = fmt.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
|
|
return
|
|
}
|
|
if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() {
|
|
err = fmt.Errorf("Read took %s; expected %s", dt, d)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestReadTimeoutFluctuation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
max := time.NewTimer(time.Second)
|
|
defer max.Stop()
|
|
ch := make(chan error)
|
|
go timeoutReader(r, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch)
|
|
|
|
select {
|
|
case <-max.C:
|
|
t.Fatal("Read took over 1s; expected 0.1s")
|
|
case err := <-ch:
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func timeoutWriter(w *os.File, d, min, max time.Duration, ch chan<- error) {
|
|
var err error
|
|
defer func() { ch <- err }()
|
|
|
|
t0 := time.Now()
|
|
if err = w.SetWriteDeadline(time.Now().Add(d)); err != nil {
|
|
return
|
|
}
|
|
var n int
|
|
for {
|
|
n, err = w.Write([]byte("TIMEOUT WRITER"))
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
t1 := time.Now()
|
|
if err == nil || !isDeadlineExceeded(err) {
|
|
err = fmt.Errorf("Write did not return (any, timeout): (%d, %v)", n, err)
|
|
return
|
|
}
|
|
if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() {
|
|
err = fmt.Errorf("Write took %s; expected %s", dt, d)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestWriteTimeoutFluctuation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
d := time.Second
|
|
max := time.NewTimer(d)
|
|
defer max.Stop()
|
|
ch := make(chan error)
|
|
go timeoutWriter(w, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch)
|
|
|
|
select {
|
|
case <-max.C:
|
|
t.Fatalf("Write took over %v; expected 0.1s", d)
|
|
case err := <-ch:
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVariousDeadlines(t *testing.T) {
|
|
t.Parallel()
|
|
testVariousDeadlines(t)
|
|
}
|
|
|
|
func TestVariousDeadlines1Proc(t *testing.T) {
|
|
// Cannot use t.Parallel - modifies global GOMAXPROCS.
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
|
testVariousDeadlines(t)
|
|
}
|
|
|
|
func TestVariousDeadlines4Proc(t *testing.T) {
|
|
// Cannot use t.Parallel - modifies global GOMAXPROCS.
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
|
testVariousDeadlines(t)
|
|
}
|
|
|
|
type neverEnding byte
|
|
|
|
func (b neverEnding) Read(p []byte) (int, error) {
|
|
for i := range p {
|
|
p[i] = byte(b)
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func testVariousDeadlines(t *testing.T) {
|
|
type result struct {
|
|
n int64
|
|
err error
|
|
d time.Duration
|
|
}
|
|
|
|
handler := func(w *os.File, pasvch chan result) {
|
|
// The writer, with no timeouts of its own,
|
|
// sending bytes to clients as fast as it can.
|
|
t0 := time.Now()
|
|
n, err := io.Copy(w, neverEnding('a'))
|
|
dt := time.Since(t0)
|
|
pasvch <- result{n, err, dt}
|
|
}
|
|
|
|
for _, timeout := range []time.Duration{
|
|
1 * time.Nanosecond,
|
|
2 * time.Nanosecond,
|
|
5 * time.Nanosecond,
|
|
50 * time.Nanosecond,
|
|
100 * time.Nanosecond,
|
|
200 * time.Nanosecond,
|
|
500 * time.Nanosecond,
|
|
750 * time.Nanosecond,
|
|
1 * time.Microsecond,
|
|
5 * time.Microsecond,
|
|
25 * time.Microsecond,
|
|
250 * time.Microsecond,
|
|
500 * time.Microsecond,
|
|
1 * time.Millisecond,
|
|
5 * time.Millisecond,
|
|
100 * time.Millisecond,
|
|
250 * time.Millisecond,
|
|
500 * time.Millisecond,
|
|
1 * time.Second,
|
|
} {
|
|
numRuns := 3
|
|
if testing.Short() {
|
|
numRuns = 1
|
|
if timeout > 500*time.Microsecond {
|
|
continue
|
|
}
|
|
}
|
|
for run := 0; run < numRuns; run++ {
|
|
t.Run(fmt.Sprintf("%v-%d", timeout, run+1), func(t *testing.T) {
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
pasvch := make(chan result)
|
|
go handler(w, pasvch)
|
|
|
|
tooLong := 5 * time.Second
|
|
max := time.NewTimer(tooLong)
|
|
defer max.Stop()
|
|
actvch := make(chan result)
|
|
go func() {
|
|
t0 := time.Now()
|
|
if err := r.SetDeadline(t0.Add(timeout)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
n, err := io.Copy(io.Discard, r)
|
|
dt := time.Since(t0)
|
|
r.Close()
|
|
actvch <- result{n, err, dt}
|
|
}()
|
|
|
|
select {
|
|
case res := <-actvch:
|
|
if !isDeadlineExceeded(err) {
|
|
t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n)
|
|
} else {
|
|
t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err)
|
|
}
|
|
case <-max.C:
|
|
t.Fatalf("timeout (%v) waiting for client to timeout (%v) reading", tooLong, timeout)
|
|
}
|
|
|
|
select {
|
|
case res := <-pasvch:
|
|
t.Logf("writer in %v wrote %d: %v", res.d, res.n, res.err)
|
|
case <-max.C:
|
|
t.Fatalf("timeout waiting for writer to finish writing")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadWriteDeadlineRace(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
N := 1000
|
|
if testing.Short() {
|
|
N = 50
|
|
}
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(3)
|
|
go func() {
|
|
defer wg.Done()
|
|
tic := time.NewTicker(2 * time.Microsecond)
|
|
defer tic.Stop()
|
|
for i := 0; i < N; i++ {
|
|
if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
|
break
|
|
}
|
|
if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
|
break
|
|
}
|
|
<-tic.C
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
var b [1]byte
|
|
for i := 0; i < N; i++ {
|
|
_, err := r.Read(b[:])
|
|
if err != nil && !isDeadlineExceeded(err) {
|
|
t.Error("Read returned non-timeout error", err)
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
var b [1]byte
|
|
for i := 0; i < N; i++ {
|
|
_, err := w.Write(b[:])
|
|
if err != nil && !isDeadlineExceeded(err) {
|
|
t.Error("Write returned non-timeout error", err)
|
|
}
|
|
}
|
|
}()
|
|
wg.Wait() // wait for tester goroutine to stop
|
|
}
|
|
|
|
// TestRacyRead tests that it is safe to mutate the input Read buffer
|
|
// immediately after cancellation has occurred.
|
|
func TestRacyRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
defer wg.Wait()
|
|
|
|
go io.Copy(w, rand.New(rand.NewSource(0)))
|
|
|
|
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
b1 := make([]byte, 1024)
|
|
b2 := make([]byte, 1024)
|
|
for j := 0; j < 100; j++ {
|
|
_, err := r.Read(b1)
|
|
copy(b1, b2) // Mutate b1 to trigger potential race
|
|
if err != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Error(err)
|
|
}
|
|
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// TestRacyWrite tests that it is safe to mutate the input Write buffer
|
|
// immediately after cancellation has occurred.
|
|
func TestRacyWrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
defer wg.Wait()
|
|
|
|
go io.Copy(io.Discard, r)
|
|
|
|
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
b1 := make([]byte, 1024)
|
|
b2 := make([]byte, 1024)
|
|
for j := 0; j < 100; j++ {
|
|
_, err := w.Write(b1)
|
|
copy(b1, b2) // Mutate b1 to trigger potential race
|
|
if err != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Error(err)
|
|
}
|
|
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// Closing a TTY while reading from it should not hang. Issue 23943.
|
|
func TestTTYClose(t *testing.T) {
|
|
// Ignore SIGTTIN in case we are running in the background.
|
|
signal.Ignore(syscall.SIGTTIN)
|
|
defer signal.Reset(syscall.SIGTTIN)
|
|
|
|
f, err := os.Open("/dev/tty")
|
|
if err != nil {
|
|
t.Skipf("skipping because opening /dev/tty failed: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
var buf [1]byte
|
|
f.Read(buf[:])
|
|
}()
|
|
|
|
// Give the goroutine a chance to enter the read.
|
|
// It doesn't matter much if it occasionally fails to do so,
|
|
// we won't be testing what we want to test but the test will pass.
|
|
time.Sleep(time.Millisecond)
|
|
|
|
c := make(chan bool)
|
|
go func() {
|
|
defer close(c)
|
|
f.Close()
|
|
}()
|
|
|
|
select {
|
|
case <-c:
|
|
case <-time.After(time.Second):
|
|
t.Error("timed out waiting for close")
|
|
}
|
|
|
|
// On some systems the goroutines may now be hanging.
|
|
// There's not much we can do about that.
|
|
}
|