1 // Copyright 2019 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.
23 "honnef.co/go/tools/internal/robustio"
26 func TestConcurrentReadsAndWrites(t *testing.T) {
27 dir, err := ioutil.TempDir("", "renameio")
31 defer os.RemoveAll(dir)
32 path := filepath.Join(dir, "blob.bin")
34 const chunkWords = 8 << 10
35 buf := make([]byte, 2*chunkWords*8)
36 for i := uint64(0); i < 2*chunkWords; i++ {
37 binary.LittleEndian.PutUint64(buf[i*8:], i)
40 var attempts int64 = 128
46 var sem = make(chan bool, parallel)
49 writeSuccesses, readSuccesses int64 // atomic
50 writeErrnoSeen, readErrnoSeen sync.Map
53 for n := attempts; n > 0; n-- {
56 defer func() { <-sem }()
58 time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
59 offset := rand.Intn(chunkWords)
60 chunk := buf[offset*8 : (offset+chunkWords)*8]
61 if err := WriteFile(path, chunk, 0666); err == nil {
62 atomic.AddInt64(&writeSuccesses, 1)
63 } else if robustio.IsEphemeralError(err) {
68 if errors.As(err, &errno) {
69 _, dup = writeErrnoSeen.LoadOrStore(errno, true)
72 t.Logf("ephemeral error: %v", err)
75 t.Errorf("unexpected error: %v", err)
78 time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
79 data, err := ReadFile(path)
81 atomic.AddInt64(&readSuccesses, 1)
82 } else if robustio.IsEphemeralError(err) {
87 if errors.As(err, &errno) {
88 _, dup = readErrnoSeen.LoadOrStore(errno, true)
91 t.Logf("ephemeral error: %v", err)
95 t.Errorf("unexpected error: %v", err)
99 if len(data) != 8*chunkWords {
100 t.Errorf("read %d bytes, but each write is a %d-byte file", len(data), 8*chunkWords)
104 u := binary.LittleEndian.Uint64(data)
105 for i := 1; i < chunkWords; i++ {
106 next := binary.LittleEndian.Uint64(data[i*8:])
108 t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i)
116 for n := parallel; n > 0; n-- {
120 var minWriteSuccesses int64 = attempts
121 if runtime.GOOS == "windows" {
122 // Windows produces frequent "Access is denied" errors under heavy rename load.
123 // As long as those are the only errors and *some* of the writes succeed, we're happy.
124 minWriteSuccesses = attempts / 4
127 if writeSuccesses < minWriteSuccesses {
128 t.Errorf("%d (of %d) writes succeeded; want ≥ %d", writeSuccesses, attempts, minWriteSuccesses)
130 t.Logf("%d (of %d) writes succeeded (ok: ≥ %d)", writeSuccesses, attempts, minWriteSuccesses)
133 var minReadSuccesses int64 = attempts
135 switch runtime.GOOS {
137 // Windows produces frequent "Access is denied" errors under heavy rename load.
138 // As long as those are the only errors and *some* of the reads succeed, we're happy.
139 minReadSuccesses = attempts / 4
142 // The filesystem on macOS 10.14 occasionally fails with "no such file or
143 // directory" errors. See https://golang.org/issue/33041. The flake rate is
144 // fairly low, so ensure that at least 75% of attempts succeed.
145 minReadSuccesses = attempts - (attempts / 4)
148 if readSuccesses < minReadSuccesses {
149 t.Errorf("%d (of %d) reads succeeded; want ≥ %d", readSuccesses, attempts, minReadSuccesses)
151 t.Logf("%d (of %d) reads succeeded (ok: ≥ %d)", readSuccesses, attempts, minReadSuccesses)