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  }