github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/internal/renameio/renameio_test.go (about) 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. 4 5 //go:build !plan9 6 // +build !plan9 7 8 package renameio 9 10 import ( 11 "encoding/binary" 12 "errors" 13 "math/rand" 14 "path/filepath" 15 "runtime" 16 "sync" 17 "sync/atomic" 18 "syscall" 19 "testing" 20 "time" 21 22 "github.com/amarpal/go-tools/internal/robustio" 23 ) 24 25 func TestConcurrentReadsAndWrites(t *testing.T) { 26 dir := t.TempDir() 27 path := filepath.Join(dir, "blob.bin") 28 29 const chunkWords = 8 << 10 30 buf := make([]byte, 2*chunkWords*8) 31 for i := uint64(0); i < 2*chunkWords; i++ { 32 binary.LittleEndian.PutUint64(buf[i*8:], i) 33 } 34 35 var attempts int64 = 128 36 if !testing.Short() { 37 attempts *= 16 38 } 39 const parallel = 32 40 41 var sem = make(chan bool, parallel) 42 43 var ( 44 writeSuccesses, readSuccesses int64 // atomic 45 writeErrnoSeen, readErrnoSeen sync.Map 46 ) 47 48 for n := attempts; n > 0; n-- { 49 sem <- true 50 go func() { 51 defer func() { <-sem }() 52 53 time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) 54 offset := rand.Intn(chunkWords) 55 chunk := buf[offset*8 : (offset+chunkWords)*8] 56 if err := WriteFile(path, chunk, 0666); err == nil { 57 atomic.AddInt64(&writeSuccesses, 1) 58 } else if robustio.IsEphemeralError(err) { 59 var ( 60 errno syscall.Errno 61 dup bool 62 ) 63 if errors.As(err, &errno) { 64 _, dup = writeErrnoSeen.LoadOrStore(errno, true) 65 } 66 if !dup { 67 t.Logf("ephemeral error: %v", err) 68 } 69 } else { 70 t.Errorf("unexpected error: %v", err) 71 } 72 73 time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond) 74 data, err := ReadFile(path) 75 if err == nil { 76 atomic.AddInt64(&readSuccesses, 1) 77 } else if robustio.IsEphemeralError(err) { 78 var ( 79 errno syscall.Errno 80 dup bool 81 ) 82 if errors.As(err, &errno) { 83 _, dup = readErrnoSeen.LoadOrStore(errno, true) 84 } 85 if !dup { 86 t.Logf("ephemeral error: %v", err) 87 } 88 return 89 } else { 90 t.Errorf("unexpected error: %v", err) 91 return 92 } 93 94 if len(data) != 8*chunkWords { 95 t.Errorf("read %d bytes, but each write is a %d-byte file", len(data), 8*chunkWords) 96 return 97 } 98 99 u := binary.LittleEndian.Uint64(data) 100 for i := 1; i < chunkWords; i++ { 101 next := binary.LittleEndian.Uint64(data[i*8:]) 102 if next != u+1 { 103 t.Errorf("wrote sequential integers, but read integer out of sequence at offset %d", i) 104 return 105 } 106 u = next 107 } 108 }() 109 } 110 111 for n := parallel; n > 0; n-- { 112 sem <- true 113 } 114 115 var minWriteSuccesses int64 = attempts 116 if runtime.GOOS == "windows" { 117 // Windows produces frequent "Access is denied" errors under heavy rename load. 118 // As long as those are the only errors and *some* of the writes succeed, we're happy. 119 minWriteSuccesses = attempts / 4 120 } 121 122 if writeSuccesses < minWriteSuccesses { 123 t.Errorf("%d (of %d) writes succeeded; want ≥ %d", writeSuccesses, attempts, minWriteSuccesses) 124 } else { 125 t.Logf("%d (of %d) writes succeeded (ok: ≥ %d)", writeSuccesses, attempts, minWriteSuccesses) 126 } 127 128 var minReadSuccesses int64 = attempts 129 130 switch runtime.GOOS { 131 case "windows": 132 // Windows produces frequent "Access is denied" errors under heavy rename load. 133 // As long as those are the only errors and *some* of the reads succeed, we're happy. 134 minReadSuccesses = attempts / 4 135 136 case "darwin": 137 // The filesystem on macOS 10.14 occasionally fails with "no such file or 138 // directory" errors. See https://golang.org/issue/33041. The flake rate is 139 // fairly low, so ensure that at least 75% of attempts succeed. 140 minReadSuccesses = attempts - (attempts / 4) 141 } 142 143 if readSuccesses < minReadSuccesses { 144 t.Errorf("%d (of %d) reads succeeded; want ≥ %d", readSuccesses, attempts, minReadSuccesses) 145 } else { 146 t.Logf("%d (of %d) reads succeeded (ok: ≥ %d)", readSuccesses, attempts, minReadSuccesses) 147 } 148 }