github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/fusetesting/parallel.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fusetesting 16 17 import ( 18 "context" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path" 23 "runtime" 24 "sync/atomic" 25 "time" 26 27 . "github.com/jacobsa/ogletest" 28 "github.com/jacobsa/syncutil" 29 ) 30 31 // Run an ogletest test that checks expectations for parallel calls to open(2) 32 // with O_CREAT. 33 func RunCreateInParallelTest_NoTruncate( 34 ctx context.Context, 35 dir string) { 36 // Ensure that we get parallelism for this test. 37 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) 38 39 // Try for awhile to see if anything breaks. 40 const duration = 500 * time.Millisecond 41 startTime := time.Now() 42 for time.Since(startTime) < duration { 43 filename := path.Join(dir, "foo") 44 45 // Set up a function that opens the file with O_CREATE and then appends a 46 // byte to it. 47 worker := func(id byte) error { 48 f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) 49 if err != nil { 50 return fmt.Errorf("Worker %d: Open: %v", id, err) 51 } 52 defer f.Close() 53 54 if _, err := f.Write([]byte{id}); err != nil { 55 return fmt.Errorf("Worker %d: Write: %v", id, err) 56 } 57 58 return nil 59 } 60 61 // Run several workers in parallel. 62 const numWorkers = 16 63 b := syncutil.NewBundle(ctx) 64 for i := 0; i < numWorkers; i++ { 65 id := byte(i) 66 b.Add(func(ctx context.Context) error { 67 return worker(id) 68 }) 69 } 70 71 err := b.Join() 72 AssertEq(nil, err) 73 74 // Read the contents of the file. We should see each worker's ID once. 75 contents, err := ioutil.ReadFile(filename) 76 AssertEq(nil, err) 77 78 idsSeen := make(map[byte]struct{}) 79 for i, _ := range contents { 80 id := contents[i] 81 AssertLt(id, numWorkers) 82 83 if _, ok := idsSeen[id]; ok { 84 AddFailure("Duplicate ID: %d", id) 85 } 86 87 idsSeen[id] = struct{}{} 88 } 89 90 AssertEq(numWorkers, len(idsSeen)) 91 92 // Delete the file. 93 err = os.Remove(filename) 94 AssertEq(nil, err) 95 } 96 } 97 98 // Run an ogletest test that checks expectations for parallel calls to open(2) 99 // with O_CREAT|O_TRUNC. 100 func RunCreateInParallelTest_Truncate( 101 ctx context.Context, 102 dir string) { 103 // Ensure that we get parallelism for this test. 104 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) 105 106 // Try for awhile to see if anything breaks. 107 const duration = 500 * time.Millisecond 108 startTime := time.Now() 109 for time.Since(startTime) < duration { 110 filename := path.Join(dir, "foo") 111 112 // Set up a function that opens the file with O_CREATE and O_TRUNC and then 113 // appends a byte to it. 114 worker := func(id byte) (err error) { 115 f, err := os.OpenFile( 116 filename, 117 os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_TRUNC, 118 0600) 119 if err != nil { 120 return fmt.Errorf("Worker %d: Open: %v", id, err) 121 } 122 defer f.Close() 123 124 if _, err := f.Write([]byte{id}); err != nil { 125 return fmt.Errorf("Worker %d: Write: %v", id, err) 126 } 127 128 return nil 129 } 130 131 // Run several workers in parallel. 132 const numWorkers = 16 133 b := syncutil.NewBundle(ctx) 134 for i := 0; i < numWorkers; i++ { 135 id := byte(i) 136 b.Add(func(ctx context.Context) error { 137 return worker(id) 138 }) 139 } 140 141 err := b.Join() 142 AssertEq(nil, err) 143 144 // Read the contents of the file. We should see at least one ID (the last 145 // one that truncated), and at most all of them. 146 contents, err := ioutil.ReadFile(filename) 147 AssertEq(nil, err) 148 149 idsSeen := make(map[byte]struct{}) 150 for i, _ := range contents { 151 id := contents[i] 152 AssertLt(id, numWorkers) 153 154 if _, ok := idsSeen[id]; ok { 155 AddFailure("Duplicate ID: %d", id) 156 } 157 158 idsSeen[id] = struct{}{} 159 } 160 161 AssertGe(len(idsSeen), 1) 162 AssertLe(len(idsSeen), numWorkers) 163 164 // Delete the file. 165 err = os.Remove(filename) 166 AssertEq(nil, err) 167 } 168 } 169 170 // Run an ogletest test that checks expectations for parallel calls to open(2) 171 // with O_CREAT|O_EXCL. 172 func RunCreateInParallelTest_Exclusive( 173 ctx context.Context, 174 dir string) { 175 // Ensure that we get parallelism for this test. 176 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) 177 178 // Try for awhile to see if anything breaks. 179 const duration = 500 * time.Millisecond 180 startTime := time.Now() 181 for time.Since(startTime) < duration { 182 filename := path.Join(dir, "foo") 183 184 // Set up a function that opens the file with O_CREATE and O_EXCL, and then 185 // appends a byte to it if it was successfully opened. 186 var openCount uint64 187 worker := func(id byte) (err error) { 188 f, err := os.OpenFile( 189 filename, 190 os.O_CREATE|os.O_EXCL|os.O_WRONLY|os.O_APPEND, 191 0600) 192 193 // If we failed to open due to the file already existing, just leave. 194 if os.IsExist(err) { 195 return nil 196 } 197 198 // Propgate other errors. 199 if err != nil { 200 return fmt.Errorf("Worker %d: Open: %v", id, err) 201 } 202 203 atomic.AddUint64(&openCount, 1) 204 defer f.Close() 205 206 if _, err := f.Write([]byte{id}); err != nil { 207 return fmt.Errorf("Worker %d: Write: %v", id, err) 208 } 209 210 return nil 211 } 212 213 // Run several workers in parallel. 214 const numWorkers = 16 215 b := syncutil.NewBundle(ctx) 216 for i := 0; i < numWorkers; i++ { 217 id := byte(i) 218 b.Add(func(ctx context.Context) error { 219 return worker(id) 220 }) 221 } 222 223 err := b.Join() 224 AssertEq(nil, err) 225 226 // Exactly one worker should have opened successfully. 227 AssertEq(1, openCount) 228 229 // Read the contents of the file. It should contain that one worker's ID. 230 contents, err := ioutil.ReadFile(filename) 231 AssertEq(nil, err) 232 233 AssertEq(1, len(contents)) 234 AssertLt(contents[0], numWorkers) 235 236 // Delete the file. 237 err = os.Remove(filename) 238 AssertEq(nil, err) 239 } 240 } 241 242 // Run an ogletest test that checks expectations for parallel calls to mkdir(2). 243 func RunMkdirInParallelTest( 244 ctx context.Context, 245 dir string) { 246 // Ensure that we get parallelism for this test. 247 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) 248 249 // Try for awhile to see if anything breaks. 250 const duration = 500 * time.Millisecond 251 startTime := time.Now() 252 for time.Since(startTime) < duration { 253 filename := path.Join(dir, "foo") 254 255 // Set up a function that creates the directory, ignoring EEXIST errors. 256 worker := func(id byte) error { 257 err := os.Mkdir(filename, 0700) 258 if os.IsExist(err) { 259 return nil 260 } 261 if err != nil { 262 return fmt.Errorf("Worker %d: Mkdir: %v", id, err) 263 } 264 265 return nil 266 } 267 268 // Run several workers in parallel. 269 const numWorkers = 16 270 b := syncutil.NewBundle(ctx) 271 for i := 0; i < numWorkers; i++ { 272 id := byte(i) 273 b.Add(func(ctx context.Context) error { 274 return worker(id) 275 }) 276 } 277 278 err := b.Join() 279 AssertEq(nil, err) 280 281 // The directory should have been created, once. 282 entries, err := ReadDirPicky(dir) 283 AssertEq(nil, err) 284 AssertEq(1, len(entries)) 285 AssertEq("foo", entries[0].Name()) 286 287 // Delete the directory. 288 err = os.Remove(filename) 289 AssertEq(nil, err) 290 } 291 } 292 293 // Run an ogletest test that checks expectations for parallel calls to 294 // symlink(2). 295 func RunSymlinkInParallelTest( 296 ctx context.Context, 297 dir string) { 298 // Ensure that we get parallelism for this test. 299 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) 300 301 // Try for awhile to see if anything breaks. 302 const duration = 500 * time.Millisecond 303 startTime := time.Now() 304 for time.Since(startTime) < duration { 305 filename := path.Join(dir, "foo") 306 307 // Set up a function that creates the symlink, ignoring EEXIST errors. 308 worker := func(id byte) error { 309 err := os.Symlink("blah", filename) 310 if os.IsExist(err) { 311 return nil 312 } 313 314 if err != nil { 315 return fmt.Errorf("Worker %d: Symlink: %v", id, err) 316 } 317 318 return nil 319 } 320 321 // Run several workers in parallel. 322 const numWorkers = 16 323 b := syncutil.NewBundle(ctx) 324 for i := 0; i < numWorkers; i++ { 325 id := byte(i) 326 b.Add(func(ctx context.Context) error { 327 return worker(id) 328 }) 329 } 330 331 err := b.Join() 332 AssertEq(nil, err) 333 334 // The symlink should have been created, once. 335 entries, err := ReadDirPicky(dir) 336 AssertEq(nil, err) 337 AssertEq(1, len(entries)) 338 AssertEq("foo", entries[0].Name()) 339 340 // Delete the directory. 341 err = os.Remove(filename) 342 AssertEq(nil, err) 343 } 344 } 345 346 // Run an ogletest test that checks expectations for parallel calls to 347 // link(2). 348 func RunHardlinkInParallelTest( 349 ctx context.Context, 350 dir string) { 351 // Ensure that we get parallelism for this test. 352 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) 353 354 // Create a file. 355 originalFile := path.Join(dir, "original_file") 356 const contents = "Hello\x00world" 357 358 err := ioutil.WriteFile(originalFile, []byte(contents), 0444) 359 AssertEq(nil, err) 360 361 // Try for awhile to see if anything breaks. 362 const duration = 500 * time.Millisecond 363 startTime := time.Now() 364 for time.Since(startTime) < duration { 365 filename := path.Join(dir, "foo") 366 367 // Set up a function that creates the symlink, ignoring EEXIST errors. 368 worker := func(id byte) error { 369 err := os.Link(originalFile, filename) 370 if os.IsExist(err) { 371 return nil 372 } 373 if err != nil { 374 return fmt.Errorf("Worker %d: Link: %v", id, err) 375 } 376 377 return nil 378 } 379 380 // Run several workers in parallel. 381 const numWorkers = 16 382 b := syncutil.NewBundle(ctx) 383 for i := 0; i < numWorkers; i++ { 384 id := byte(i) 385 b.Add(func(ctx context.Context) error { 386 return worker(id) 387 }) 388 } 389 390 err := b.Join() 391 AssertEq(nil, err) 392 393 // The symlink should have been created, once. 394 entries, err := ReadDirPicky(dir) 395 AssertEq(nil, err) 396 AssertEq(2, len(entries)) 397 AssertEq("foo", entries[0].Name()) 398 AssertEq("original_file", entries[1].Name()) 399 400 // Remove the link. 401 err = os.Remove(filename) 402 AssertEq(nil, err) 403 } 404 405 // Clean up the original file at the end. 406 err = os.Remove(originalFile) 407 AssertEq(nil, err) 408 }