Source file src/os/timeout_test.go

     1  // Copyright 2017 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 !js && !plan9 && !wasip1
     6  
     7  package os_test
     8  
     9  import (
    10  	"fmt"
    11  	"io"
    12  	"math/rand"
    13  	"os"
    14  	"runtime"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  )
    19  
    20  func TestNonpollableDeadline(t *testing.T) {
    21  	// On BSD systems regular files seem to be pollable,
    22  	// so just run this test on Linux and Windows.
    23  	if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
    24  		t.Skipf("skipping on %s", runtime.GOOS)
    25  	}
    26  	t.Parallel()
    27  
    28  	f, err := os.CreateTemp("", "ostest")
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  	defer os.Remove(f.Name())
    33  	defer f.Close()
    34  	deadline := time.Now().Add(10 * time.Second)
    35  	if err := f.SetDeadline(deadline); err != os.ErrNoDeadline {
    36  		t.Errorf("SetDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
    37  	}
    38  	if err := f.SetReadDeadline(deadline); err != os.ErrNoDeadline {
    39  		t.Errorf("SetReadDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
    40  	}
    41  	if err := f.SetWriteDeadline(deadline); err != os.ErrNoDeadline {
    42  		t.Errorf("SetWriteDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
    43  	}
    44  }
    45  
    46  type pipeDeadlineTest struct {
    47  	name   string
    48  	create func(t *testing.T) (r, w *os.File)
    49  }
    50  
    51  var pipeDeadlinesTestCases []pipeDeadlineTest
    52  
    53  // noDeadline is a zero time.Time value, which cancels a deadline.
    54  var noDeadline time.Time
    55  
    56  var readTimeoutTests = []struct {
    57  	timeout time.Duration
    58  	xerrs   [2]error // expected errors in transition
    59  }{
    60  	// Tests that read deadlines work, even if there's data ready
    61  	// to be read.
    62  	{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
    63  
    64  	{50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
    65  }
    66  
    67  // There is a very similar copy of this in net/timeout_test.go.
    68  func TestReadTimeout(t *testing.T) {
    69  	t.Parallel()
    70  
    71  	for _, tc := range pipeDeadlinesTestCases {
    72  		t.Run(tc.name, func(t *testing.T) {
    73  			t.Parallel()
    74  
    75  			r, w := tc.create(t)
    76  			defer r.Close()
    77  			defer w.Close()
    78  
    79  			if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil {
    80  				t.Fatal(err)
    81  			}
    82  
    83  			for i, tt := range readTimeoutTests {
    84  				if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil {
    85  					t.Fatalf("#%d: %v", i, err)
    86  				}
    87  				var b [1]byte
    88  				for j, xerr := range tt.xerrs {
    89  					for {
    90  						n, err := r.Read(b[:])
    91  						if xerr != nil {
    92  							if !isDeadlineExceeded(err) {
    93  								t.Fatalf("#%d/%d: %v", i, j, err)
    94  							}
    95  						}
    96  						if err == nil {
    97  							time.Sleep(tt.timeout / 3)
    98  							continue
    99  						}
   100  						if n != 0 {
   101  							t.Fatalf("#%d/%d: read %d; want 0", i, j, n)
   102  						}
   103  						break
   104  					}
   105  				}
   106  			}
   107  		})
   108  	}
   109  }
   110  
   111  // There is a very similar copy of this in net/timeout_test.go.
   112  func TestReadTimeoutMustNotReturn(t *testing.T) {
   113  	t.Parallel()
   114  
   115  	for _, tc := range pipeDeadlinesTestCases {
   116  		t.Run(tc.name, func(t *testing.T) {
   117  			t.Parallel()
   118  
   119  			r, w := tc.create(t)
   120  			defer r.Close()
   121  			defer w.Close()
   122  
   123  			max := time.NewTimer(100 * time.Millisecond)
   124  			defer max.Stop()
   125  			ch := make(chan error)
   126  			go func() {
   127  				if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
   128  					t.Error(err)
   129  				}
   130  				if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil {
   131  					t.Error(err)
   132  				}
   133  				if err := r.SetReadDeadline(noDeadline); err != nil {
   134  					t.Error(err)
   135  				}
   136  				var b [1]byte
   137  				_, err := r.Read(b[:])
   138  				ch <- err
   139  			}()
   140  
   141  			select {
   142  			case err := <-ch:
   143  				t.Fatalf("expected Read to not return, but it returned with %v", err)
   144  			case <-max.C:
   145  				w.Close()
   146  				err := <-ch // wait for tester goroutine to stop
   147  				if os.IsTimeout(err) {
   148  					t.Fatal(err)
   149  				}
   150  			}
   151  		})
   152  	}
   153  }
   154  
   155  var writeTimeoutTests = []struct {
   156  	timeout time.Duration
   157  	xerrs   [2]error // expected errors in transition
   158  }{
   159  	// Tests that write deadlines work, even if there's buffer
   160  	// space available to write.
   161  	{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
   162  
   163  	{10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
   164  }
   165  
   166  // There is a very similar copy of this in net/timeout_test.go.
   167  func TestWriteTimeout(t *testing.T) {
   168  	t.Parallel()
   169  
   170  	for _, tc := range pipeDeadlinesTestCases {
   171  		t.Run(tc.name, func(t *testing.T) {
   172  			t.Parallel()
   173  
   174  			for i, tt := range writeTimeoutTests {
   175  				t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
   176  					r, w := tc.create(t)
   177  					defer r.Close()
   178  					defer w.Close()
   179  
   180  					if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
   181  						t.Fatalf("%v", err)
   182  					}
   183  					for j, xerr := range tt.xerrs {
   184  						for {
   185  							n, err := w.Write([]byte("WRITE TIMEOUT TEST"))
   186  							if xerr != nil {
   187  								if !isDeadlineExceeded(err) {
   188  									t.Fatalf("%d: %v", j, err)
   189  								}
   190  							}
   191  							if err == nil {
   192  								time.Sleep(tt.timeout / 3)
   193  								continue
   194  							}
   195  							if n != 0 {
   196  								t.Fatalf("%d: wrote %d; want 0", j, n)
   197  							}
   198  							break
   199  						}
   200  					}
   201  				})
   202  			}
   203  		})
   204  	}
   205  }
   206  
   207  // There is a very similar copy of this in net/timeout_test.go.
   208  func TestWriteTimeoutMustNotReturn(t *testing.T) {
   209  	t.Parallel()
   210  
   211  	for _, tc := range pipeDeadlinesTestCases {
   212  		t.Run(tc.name, func(t *testing.T) {
   213  			t.Parallel()
   214  
   215  			r, w := tc.create(t)
   216  			defer r.Close()
   217  			defer w.Close()
   218  
   219  			max := time.NewTimer(100 * time.Millisecond)
   220  			defer max.Stop()
   221  			ch := make(chan error)
   222  			go func() {
   223  				if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
   224  					t.Error(err)
   225  				}
   226  				if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil {
   227  					t.Error(err)
   228  				}
   229  				if err := w.SetWriteDeadline(noDeadline); err != nil {
   230  					t.Error(err)
   231  				}
   232  				var b [1]byte
   233  				for {
   234  					if _, err := w.Write(b[:]); err != nil {
   235  						ch <- err
   236  						break
   237  					}
   238  				}
   239  			}()
   240  
   241  			select {
   242  			case err := <-ch:
   243  				t.Fatalf("expected Write to not return, but it returned with %v", err)
   244  			case <-max.C:
   245  				r.Close()
   246  				err := <-ch // wait for tester goroutine to stop
   247  				if os.IsTimeout(err) {
   248  					t.Fatal(err)
   249  				}
   250  			}
   251  		})
   252  	}
   253  }
   254  
   255  const (
   256  	// minDynamicTimeout is the minimum timeout to attempt for
   257  	// tests that automatically increase timeouts until success.
   258  	//
   259  	// Lower values may allow tests to succeed more quickly if the value is close
   260  	// to the true minimum, but may require more iterations (and waste more time
   261  	// and CPU power on failed attempts) if the timeout is too low.
   262  	minDynamicTimeout = 1 * time.Millisecond
   263  
   264  	// maxDynamicTimeout is the maximum timeout to attempt for
   265  	// tests that automatically increase timeouts until success.
   266  	//
   267  	// This should be a strict upper bound on the latency required to hit a
   268  	// timeout accurately, even on a slow or heavily-loaded machine. If a test
   269  	// would increase the timeout beyond this value, the test fails.
   270  	maxDynamicTimeout = 4 * time.Second
   271  )
   272  
   273  // timeoutUpperBound returns the maximum time that we expect a timeout of
   274  // duration d to take to return the caller.
   275  func timeoutUpperBound(d time.Duration) time.Duration {
   276  	switch runtime.GOOS {
   277  	case "openbsd", "netbsd":
   278  		// NetBSD and OpenBSD seem to be unable to reliably hit deadlines even when
   279  		// the absolute durations are long.
   280  		// In https://build.golang.org/log/c34f8685d020b98377dd4988cd38f0c5bd72267e,
   281  		// we observed that an openbsd-amd64-68 builder took 4.090948779s for a
   282  		// 2.983020682s timeout (37.1% overhead).
   283  		// (See https://go.dev/issue/50189 for further detail.)
   284  		// Give them lots of slop to compensate.
   285  		return d * 3 / 2
   286  	}
   287  	// Other platforms seem to hit their deadlines more reliably,
   288  	// at least when they are long enough to cover scheduling jitter.
   289  	return d * 11 / 10
   290  }
   291  
   292  // nextTimeout returns the next timeout to try after an operation took the given
   293  // actual duration with a timeout shorter than that duration.
   294  func nextTimeout(actual time.Duration) (next time.Duration, ok bool) {
   295  	if actual >= maxDynamicTimeout {
   296  		return maxDynamicTimeout, false
   297  	}
   298  	// Since the previous attempt took actual, we can't expect to beat that
   299  	// duration by any significant margin. Try the next attempt with an arbitrary
   300  	// factor above that, so that our growth curve is at least exponential.
   301  	next = actual * 5 / 4
   302  	return min(next, maxDynamicTimeout), true
   303  }
   304  
   305  // There is a very similar copy of this in net/timeout_test.go.
   306  func TestReadTimeoutFluctuation(t *testing.T) {
   307  	t.Parallel()
   308  
   309  	for _, tc := range pipeDeadlinesTestCases {
   310  		t.Run(tc.name, func(t *testing.T) {
   311  			t.Parallel()
   312  
   313  			r, w := tc.create(t)
   314  			defer r.Close()
   315  			defer w.Close()
   316  
   317  			d := minDynamicTimeout
   318  			b := make([]byte, 256)
   319  			for {
   320  				t.Logf("SetReadDeadline(+%v)", d)
   321  				t0 := time.Now()
   322  				deadline := t0.Add(d)
   323  				if err := r.SetReadDeadline(deadline); err != nil {
   324  					t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
   325  				}
   326  				var n int
   327  				n, err := r.Read(b)
   328  				t1 := time.Now()
   329  
   330  				if n != 0 || err == nil || !isDeadlineExceeded(err) {
   331  					t.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
   332  				}
   333  
   334  				actual := t1.Sub(t0)
   335  				if t1.Before(deadline) {
   336  					t.Errorf("Read took %s; expected at least %s", actual, d)
   337  				}
   338  				if t.Failed() {
   339  					return
   340  				}
   341  				if want := timeoutUpperBound(d); actual > want {
   342  					next, ok := nextTimeout(actual)
   343  					if !ok {
   344  						t.Fatalf("Read took %s; expected at most %v", actual, want)
   345  					}
   346  					// Maybe this machine is too slow to reliably schedule goroutines within
   347  					// the requested duration. Increase the timeout and try again.
   348  					t.Logf("Read took %s (expected %s); trying with longer timeout", actual, d)
   349  					d = next
   350  					continue
   351  				}
   352  
   353  				break
   354  			}
   355  		})
   356  	}
   357  }
   358  
   359  // There is a very similar copy of this in net/timeout_test.go.
   360  func TestWriteTimeoutFluctuation(t *testing.T) {
   361  	t.Parallel()
   362  
   363  	for _, tc := range pipeDeadlinesTestCases {
   364  		t.Run(tc.name, func(t *testing.T) {
   365  			t.Parallel()
   366  
   367  			r, w := tc.create(t)
   368  			defer r.Close()
   369  			defer w.Close()
   370  
   371  			d := minDynamicTimeout
   372  			for {
   373  				t.Logf("SetWriteDeadline(+%v)", d)
   374  				t0 := time.Now()
   375  				deadline := t0.Add(d)
   376  				if err := w.SetWriteDeadline(deadline); err != nil {
   377  					t.Fatalf("SetWriteDeadline(%v): %v", deadline, err)
   378  				}
   379  				var n int64
   380  				var err error
   381  				for {
   382  					var dn int
   383  					dn, err = w.Write([]byte("TIMEOUT TRANSMITTER"))
   384  					n += int64(dn)
   385  					if err != nil {
   386  						break
   387  					}
   388  				}
   389  				t1 := time.Now()
   390  				// Inv: err != nil
   391  				if !isDeadlineExceeded(err) {
   392  					t.Fatalf("Write did not return (any, timeout): (%d, %v)", n, err)
   393  				}
   394  
   395  				actual := t1.Sub(t0)
   396  				if t1.Before(deadline) {
   397  					t.Errorf("Write took %s; expected at least %s", actual, d)
   398  				}
   399  				if t.Failed() {
   400  					return
   401  				}
   402  				if want := timeoutUpperBound(d); actual > want {
   403  					if n > 0 {
   404  						// SetWriteDeadline specifies a time “after which I/O operations fail
   405  						// instead of blocking”. However, the kernel's send buffer is not yet
   406  						// full, we may be able to write some arbitrary (but finite) number of
   407  						// bytes to it without blocking.
   408  						t.Logf("Wrote %d bytes into send buffer; retrying until buffer is full", n)
   409  						if d <= maxDynamicTimeout/2 {
   410  							// We don't know how long the actual write loop would have taken if
   411  							// the buffer were full, so just guess and double the duration so that
   412  							// the next attempt can make twice as much progress toward filling it.
   413  							d *= 2
   414  						}
   415  					} else if next, ok := nextTimeout(actual); !ok {
   416  						t.Fatalf("Write took %s; expected at most %s", actual, want)
   417  					} else {
   418  						// Maybe this machine is too slow to reliably schedule goroutines within
   419  						// the requested duration. Increase the timeout and try again.
   420  						t.Logf("Write took %s (expected %s); trying with longer timeout", actual, d)
   421  						d = next
   422  					}
   423  					continue
   424  				}
   425  
   426  				break
   427  			}
   428  		})
   429  	}
   430  }
   431  
   432  // There is a very similar copy of this in net/timeout_test.go.
   433  func TestVariousDeadlines(t *testing.T) {
   434  	t.Parallel()
   435  	for _, tc := range pipeDeadlinesTestCases {
   436  		t.Run(tc.name, func(t *testing.T) {
   437  			t.Parallel()
   438  			testVariousDeadlines(t, tc.create)
   439  		})
   440  	}
   441  }
   442  
   443  // There is a very similar copy of this in net/timeout_test.go.
   444  func TestVariousDeadlines1Proc(t *testing.T) {
   445  	// Cannot use t.Parallel - modifies global GOMAXPROCS.
   446  	if testing.Short() {
   447  		t.Skip("skipping in short mode")
   448  	}
   449  	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
   450  	for _, tc := range pipeDeadlinesTestCases {
   451  		t.Run(tc.name, func(t *testing.T) {
   452  			t.Parallel()
   453  			testVariousDeadlines(t, tc.create)
   454  		})
   455  	}
   456  }
   457  
   458  // There is a very similar copy of this in net/timeout_test.go.
   459  func TestVariousDeadlines4Proc(t *testing.T) {
   460  	// Cannot use t.Parallel - modifies global GOMAXPROCS.
   461  	if testing.Short() {
   462  		t.Skip("skipping in short mode")
   463  	}
   464  	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
   465  	for _, tc := range pipeDeadlinesTestCases {
   466  		t.Run(tc.name, func(t *testing.T) {
   467  			t.Parallel()
   468  			testVariousDeadlines(t, tc.create)
   469  		})
   470  	}
   471  }
   472  
   473  type neverEnding byte
   474  
   475  func (b neverEnding) Read(p []byte) (int, error) {
   476  	for i := range p {
   477  		p[i] = byte(b)
   478  	}
   479  	return len(p), nil
   480  }
   481  
   482  func testVariousDeadlines(t *testing.T, create func(t *testing.T) (r, w *os.File)) {
   483  	type result struct {
   484  		n   int64
   485  		err error
   486  		d   time.Duration
   487  	}
   488  
   489  	handler := func(w *os.File, pasvch chan result) {
   490  		// The writer, with no timeouts of its own,
   491  		// sending bytes to clients as fast as it can.
   492  		t0 := time.Now()
   493  		n, err := io.Copy(w, neverEnding('a'))
   494  		dt := time.Since(t0)
   495  		pasvch <- result{n, err, dt}
   496  	}
   497  
   498  	for _, timeout := range []time.Duration{
   499  		1 * time.Nanosecond,
   500  		2 * time.Nanosecond,
   501  		5 * time.Nanosecond,
   502  		50 * time.Nanosecond,
   503  		100 * time.Nanosecond,
   504  		200 * time.Nanosecond,
   505  		500 * time.Nanosecond,
   506  		750 * time.Nanosecond,
   507  		1 * time.Microsecond,
   508  		5 * time.Microsecond,
   509  		25 * time.Microsecond,
   510  		250 * time.Microsecond,
   511  		500 * time.Microsecond,
   512  		1 * time.Millisecond,
   513  		5 * time.Millisecond,
   514  		100 * time.Millisecond,
   515  		250 * time.Millisecond,
   516  		500 * time.Millisecond,
   517  		1 * time.Second,
   518  	} {
   519  		numRuns := 3
   520  		if testing.Short() {
   521  			numRuns = 1
   522  			if timeout > 500*time.Microsecond {
   523  				continue
   524  			}
   525  		}
   526  		for run := 0; run < numRuns; run++ {
   527  			t.Run(fmt.Sprintf("%v-%d", timeout, run+1), func(t *testing.T) {
   528  				r, w := create(t)
   529  				defer r.Close()
   530  				defer w.Close()
   531  
   532  				pasvch := make(chan result)
   533  				go handler(w, pasvch)
   534  
   535  				tooLong := 5 * time.Second
   536  				max := time.NewTimer(tooLong)
   537  				defer max.Stop()
   538  				actvch := make(chan result)
   539  				go func() {
   540  					t0 := time.Now()
   541  					if err := r.SetDeadline(t0.Add(timeout)); err != nil {
   542  						t.Error(err)
   543  					}
   544  					n, err := io.Copy(io.Discard, r)
   545  					dt := time.Since(t0)
   546  					r.Close()
   547  					actvch <- result{n, err, dt}
   548  				}()
   549  
   550  				select {
   551  				case res := <-actvch:
   552  					if isDeadlineExceeded(res.err) {
   553  						t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n)
   554  					} else {
   555  						t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err)
   556  					}
   557  				case <-max.C:
   558  					t.Fatalf("timeout (%v) waiting for client to timeout (%v) reading", tooLong, timeout)
   559  				}
   560  
   561  				select {
   562  				case res := <-pasvch:
   563  					t.Logf("writer in %v wrote %d: %v", res.d, res.n, res.err)
   564  				case <-max.C:
   565  					t.Fatalf("timeout waiting for writer to finish writing")
   566  				}
   567  			})
   568  		}
   569  	}
   570  }
   571  
   572  // There is a very similar copy of this in net/timeout_test.go.
   573  func TestReadWriteDeadlineRace(t *testing.T) {
   574  	t.Parallel()
   575  
   576  	N := 1000
   577  	if testing.Short() {
   578  		N = 50
   579  	}
   580  
   581  	for _, tc := range pipeDeadlinesTestCases {
   582  		t.Run(tc.name, func(t *testing.T) {
   583  			t.Parallel()
   584  
   585  			r, w := tc.create(t)
   586  			defer r.Close()
   587  			defer w.Close()
   588  
   589  			var wg sync.WaitGroup
   590  			wg.Add(3)
   591  			go func() {
   592  				defer wg.Done()
   593  				tic := time.NewTicker(2 * time.Microsecond)
   594  				defer tic.Stop()
   595  				for i := 0; i < N; i++ {
   596  					if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
   597  						break
   598  					}
   599  					if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
   600  						break
   601  					}
   602  					<-tic.C
   603  				}
   604  			}()
   605  			go func() {
   606  				defer wg.Done()
   607  				var b [1]byte
   608  				for i := 0; i < N; i++ {
   609  					_, err := r.Read(b[:])
   610  					if err != nil && !isDeadlineExceeded(err) {
   611  						t.Error("Read returned non-timeout error", err)
   612  					}
   613  				}
   614  			}()
   615  			go func() {
   616  				defer wg.Done()
   617  				var b [1]byte
   618  				for i := 0; i < N; i++ {
   619  					_, err := w.Write(b[:])
   620  					if err != nil && !isDeadlineExceeded(err) {
   621  						t.Error("Write returned non-timeout error", err)
   622  					}
   623  				}
   624  			}()
   625  			wg.Wait() // wait for tester goroutine to stop
   626  		})
   627  	}
   628  }
   629  
   630  // TestRacyRead tests that it is safe to mutate the input Read buffer
   631  // immediately after cancellation has occurred.
   632  func TestRacyRead(t *testing.T) {
   633  	t.Parallel()
   634  
   635  	for _, tc := range pipeDeadlinesTestCases {
   636  		t.Run(tc.name, func(t *testing.T) {
   637  			t.Parallel()
   638  
   639  			r, w := tc.create(t)
   640  			defer r.Close()
   641  			defer w.Close()
   642  
   643  			var wg sync.WaitGroup
   644  			defer wg.Wait()
   645  
   646  			go io.Copy(w, rand.New(rand.NewSource(0)))
   647  
   648  			r.SetReadDeadline(time.Now().Add(time.Millisecond))
   649  			for i := 0; i < 10; i++ {
   650  				wg.Add(1)
   651  				go func() {
   652  					defer wg.Done()
   653  
   654  					b1 := make([]byte, 1024)
   655  					b2 := make([]byte, 1024)
   656  					for j := 0; j < 100; j++ {
   657  						_, err := r.Read(b1)
   658  						copy(b1, b2) // Mutate b1 to trigger potential race
   659  						if err != nil {
   660  							if !isDeadlineExceeded(err) {
   661  								t.Error(err)
   662  							}
   663  							r.SetReadDeadline(time.Now().Add(time.Millisecond))
   664  						}
   665  					}
   666  				}()
   667  			}
   668  		})
   669  	}
   670  }
   671  
   672  // TestRacyWrite tests that it is safe to mutate the input Write buffer
   673  // immediately after cancellation has occurred.
   674  func TestRacyWrite(t *testing.T) {
   675  	t.Parallel()
   676  
   677  	for _, tc := range pipeDeadlinesTestCases {
   678  		t.Run(tc.name, func(t *testing.T) {
   679  			t.Parallel()
   680  
   681  			r, w := tc.create(t)
   682  			defer r.Close()
   683  			defer w.Close()
   684  
   685  			var wg sync.WaitGroup
   686  			defer wg.Wait()
   687  
   688  			go io.Copy(io.Discard, r)
   689  
   690  			w.SetWriteDeadline(time.Now().Add(time.Millisecond))
   691  			for i := 0; i < 10; i++ {
   692  				wg.Add(1)
   693  				go func() {
   694  					defer wg.Done()
   695  
   696  					b1 := make([]byte, 1024)
   697  					b2 := make([]byte, 1024)
   698  					for j := 0; j < 100; j++ {
   699  						_, err := w.Write(b1)
   700  						copy(b1, b2) // Mutate b1 to trigger potential race
   701  						if err != nil {
   702  							if !isDeadlineExceeded(err) {
   703  								t.Error(err)
   704  							}
   705  							w.SetWriteDeadline(time.Now().Add(time.Millisecond))
   706  						}
   707  					}
   708  				}()
   709  			}
   710  		})
   711  	}
   712  }
   713  

View as plain text