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