From f2d894194d7d5eace129818e912dec3d4ce7d41b Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Sat, 21 Dec 2024 09:40:37 +0000 Subject: [PATCH] Avoid syscall. --- .github/workflows/test.yml | 4 ++ tests/parallel/parallel_test.go | 69 ++++++++++++++++++++++++++++++--- vfs/shm_bsd.go | 16 ++++++-- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2dc9d76..68b4406 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,6 +66,10 @@ jobs: shell: bash run: gormlite/test.sh + - name: Test modules + shell: bash + run: go test -v ./embed/bcw2/... + - name: Collect coverage run: go run github.com/dave/courtney@latest if: | diff --git a/tests/parallel/parallel_test.go b/tests/parallel/parallel_test.go index 836a3e2..2c8da8c 100644 --- a/tests/parallel/parallel_test.go +++ b/tests/parallel/parallel_test.go @@ -140,7 +140,7 @@ func Test_xts(t *testing.T) { testIntegrity(t, name) } -func TestMultiProcess(t *testing.T) { +func Test_MultiProcess_rollback(t *testing.T) { if !vfs.SupportsFileLocking { t.Skip("skipping without locks") } @@ -149,7 +149,7 @@ func TestMultiProcess(t *testing.T) { } file := filepath.Join(t.TempDir(), "test.db") - t.Setenv("TestMultiProcess_dbfile", file) + t.Setenv("Test_MultiProcess_dbfile", file) name := "file:" + filepath.ToSlash(file) + "?_pragma=busy_timeout(10000)" + @@ -161,7 +161,7 @@ func TestMultiProcess(t *testing.T) { t.Fatal(err) } - cmd := exec.Command(exe, append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...) + cmd := exec.Command(exe, append(os.Args[1:], "-test.v", "-test.run=Test_ChildProcess_rollback")...) out, err := cmd.StdoutPipe() if err != nil { t.Fatal(err) @@ -185,8 +185,8 @@ func TestMultiProcess(t *testing.T) { testIntegrity(t, name) } -func TestChildProcess(t *testing.T) { - file := os.Getenv("TestMultiProcess_dbfile") +func Test_ChildProcess_rollback(t *testing.T) { + file := os.Getenv("Test_MultiProcess_dbfile") if file == "" || testing.Short() { t.SkipNow() } @@ -199,6 +199,65 @@ func TestChildProcess(t *testing.T) { testParallel(t, name, 1000) } +func Test_MultiProcess_wal(t *testing.T) { + if !vfs.SupportsFileLocking { + t.Skip("skipping without locks") + } + if testing.Short() { + t.Skip("skipping in short mode") + } + + file := filepath.Join(t.TempDir(), "test.db") + t.Setenv("Test_MultiProcess_dbfile", file) + + name := "file:" + filepath.ToSlash(file) + + "?_pragma=busy_timeout(10000)" + + "&_pragma=journal_mode(wal)" + + "&_pragma=synchronous(off)" + + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + + cmd := exec.Command(exe, append(os.Args[1:], "-test.v", "-test.run=Test_ChildProcess_wal")...) + out, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + var buf [3]byte + // Wait for child to start. + if _, err := io.ReadFull(out, buf[:]); err != nil { + t.Fatal(err) + } else if str := string(buf[:]); str != "===" { + t.Fatal(str) + } + + testParallel(t, name, 1000) + if err := cmd.Wait(); err != nil { + t.Error(err) + } + testIntegrity(t, name) +} + +func Test_ChildProcess_wal(t *testing.T) { + file := os.Getenv("Test_MultiProcess_dbfile") + if file == "" || testing.Short() { + t.SkipNow() + } + + name := "file:" + filepath.ToSlash(file) + + "?_pragma=busy_timeout(10000)" + + "&_pragma=journal_mode(wal)" + + "&_pragma=synchronous(off)" + + testParallel(t, name, 1000) +} + func Benchmark_parallel(b *testing.B) { if !vfs.SupportsSharedMemory { b.Skip("skipping without shared memory") diff --git a/vfs/shm_bsd.go b/vfs/shm_bsd.go index 5f4f5d1..76e6888 100644 --- a/vfs/shm_bsd.go +++ b/vfs/shm_bsd.go @@ -178,7 +178,7 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { s.Lock() defer s.Unlock() - // Check if we could obtain/release the lock locally. + // Check if we can obtain/release locks locally. rc := s.shmMemLock(offset, n, flags) if rc != _OK { return rc @@ -187,6 +187,8 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { // Obtain/release the appropriate file locks. switch { case flags&_SHM_UNLOCK != 0: + // Relasing a shared lock decrements the counter, + // but may leave parts of the range still locked. begin, end := offset, offset+n for i := begin; i < end; i++ { if s.vfsShmParent.lock[i] != 0 { @@ -201,14 +203,22 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { } return rc case flags&_SHM_SHARED != 0: - rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n)) + // Acquiring a new shared lock on the file is only necessary + // if there was a new shared lock in the range. + for i := offset; i < offset+n; i++ { + if s.vfsShmParent.lock[i] == 1 { + rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n)) + break + } + } case flags&_SHM_EXCLUSIVE != 0: + // Acquiring an exclusive lock on the file is always necessary. rc = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n)) default: panic(util.AssertErr()) } - // Release the local lock we had acquired. + // Release the local locks we had acquired. if rc != _OK { s.shmMemLock(offset, n, flags^(_SHM_UNLOCK|_SHM_LOCK)) }