aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2023-06-05 12:42:33 +0200
committerJakob Unterwurzacher2023-06-06 16:24:06 +0200
commit344917ca1be87421f4120187820adb2a41fb130b (patch)
treeafcc61b335e88dea9d9879a20abf4e8ed0484f6b
parent964f0c190932e5dc53b05ec69ccda6e8d33a73b6 (diff)
tests/cluster: add TestConcurrentCreate
This exercises the byte-range locks we just added.
-rw-r--r--tests/cluster/cluster_test.go75
-rw-r--r--tests/test_helpers/helpers.go2
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 {