diff options
author | Jakob Unterwurzacher | 2023-06-05 12:42:33 +0200 |
---|---|---|
committer | Jakob Unterwurzacher | 2023-06-06 16:24:06 +0200 |
commit | 344917ca1be87421f4120187820adb2a41fb130b (patch) | |
tree | afcc61b335e88dea9d9879a20abf4e8ed0484f6b | |
parent | 964f0c190932e5dc53b05ec69ccda6e8d33a73b6 (diff) |
tests/cluster: add TestConcurrentCreate
This exercises the byte-range locks we just added.
-rw-r--r-- | tests/cluster/cluster_test.go | 75 | ||||
-rw-r--r-- | tests/test_helpers/helpers.go | 2 |
2 files changed, 76 insertions, 1 deletions
diff --git a/tests/cluster/cluster_test.go b/tests/cluster/cluster_test.go index def03f1..23e7c50 100644 --- a/tests/cluster/cluster_test.go +++ b/tests/cluster/cluster_test.go @@ -7,6 +7,7 @@ package cluster_test import ( "bytes" + "errors" "math/rand" "os" "sync" @@ -15,6 +16,7 @@ import ( "golang.org/x/sys/unix" + "github.com/rfjakob/gocryptfs/v2/internal/contentenc" "github.com/rfjakob/gocryptfs/v2/tests/test_helpers" ) @@ -39,7 +41,7 @@ func TestClusterConcurrentRW(t *testing.T) { "Choose a backing directory by setting TMPDIR.") } - const blocksize = 4096 + const blocksize = contentenc.DefaultBS const fileSize = 25 * blocksize // 100 kiB cDir := test_helpers.InitFS(t) @@ -113,6 +115,77 @@ func TestClusterConcurrentRW(t *testing.T) { wg.Wait() } +// Multiple hosts creating the same file at the same time could +// overwrite each other's file header, leading to data +// corruption. Passing "-sharedstorage" should prevent this. +func TestConcurrentCreate(t *testing.T) { + cDir := test_helpers.InitFS(t) + mnt1 := cDir + ".mnt1" + mnt2 := cDir + ".mnt2" + test_helpers.MountOrFatal(t, cDir, mnt1, "-extpass=echo test", "-wpanic=0", "-sharedstorage") + defer test_helpers.UnmountPanic(mnt1) + test_helpers.MountOrFatal(t, cDir, mnt2, "-extpass=echo test", "-wpanic=0", "-sharedstorage") + defer test_helpers.UnmountPanic(mnt2) + + var wg sync.WaitGroup + const loops = 10000 + + createOrOpen := func(path string) (f *os.File, err error) { + // Use the high-level os.Create/OpenFile instead of syscall.Open because we + // *want* Go's EINTR retry logic. glibc open(2) has similar logic. + f, err = os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_EXCL, 0600) + if err == nil { + return + } + if !errors.Is(err, os.ErrExist) { + t.Logf("POSIX compliance issue: exclusive create failed with unexpected error: err=%v", errors.Unwrap(err)) + } + f, err = os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + if err == nil { + return + } + t.Logf("POSIX compliance issue: non-exlusive create failed with err=%v", errors.Unwrap(err)) + return + } + + workerThread := func(path string) { + defer wg.Done() + buf := make([]byte, 10) + buf2 := make([]byte, 10) + for i := 0; i < loops; i++ { + if t.Failed() { + return + } + f, err := createOrOpen(path) + if err != nil { + // retry + continue + } + defer f.Close() + _, err = f.WriteAt(buf, 0) + if err != nil { + t.Errorf("iteration %d: Pwrite: %v", i, err) + return + } + _, err = f.ReadAt(buf2, 0) + if err != nil { + t.Errorf("iteration %d: ReadAt: %v", i, err) + return + } + if !bytes.Equal(buf, buf2) { + t.Errorf("iteration %d: corrupt data received: %x", i, buf2) + return + } + syscall.Unlink(path) + } + } + + wg.Add(2) + go workerThread(mnt1 + "/foo") + go workerThread(mnt2 + "/foo") + wg.Wait() +} + // Check that byte-range locks work on an empty file func TestFcntlFlock(t *testing.T) { path := test_helpers.TmpDir + "/" + t.Name() diff --git a/tests/test_helpers/helpers.go b/tests/test_helpers/helpers.go index 0d21548..b3594e6 100644 --- a/tests/test_helpers/helpers.go +++ b/tests/test_helpers/helpers.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "syscall" "testing" @@ -147,6 +148,7 @@ func InitFS(t *testing.T, extraArgs ...string) string { prefix := "x." if t != nil { prefix = t.Name() + "." + prefix = strings.ReplaceAll(prefix, "/", "_") } dir, err := ioutil.TempDir(TmpDir, prefix) if err != nil { |