diff --git a/embed/bcw2/bcw2.wasm b/embed/bcw2/bcw2.wasm index 4d4439c..5a26073 100755 Binary files a/embed/bcw2/bcw2.wasm and b/embed/bcw2/bcw2.wasm differ diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index 5f4b64a..353feac 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/sqlite3/sqlite_opt.h b/sqlite3/sqlite_opt.h index bf47bf8..7015ebb 100644 --- a/sqlite3/sqlite_opt.h +++ b/sqlite3/sqlite_opt.h @@ -24,6 +24,7 @@ #define SQLITE_ENABLE_ATOMIC_WRITE #define SQLITE_ENABLE_BATCH_ATOMIC_WRITE #define SQLITE_ENABLE_COLUMN_METADATA +#define SQLITE_ENABLE_SETLK_TIMEOUT 2 #define SQLITE_ENABLE_STAT4 1 // We have our own memdb VFS. diff --git a/vfs/lock.go b/vfs/lock.go index 86a988a..5366fdb 100644 --- a/vfs/lock.go +++ b/vfs/lock.go @@ -79,16 +79,15 @@ func (f *vfsFile) Lock(lock LockLevel) error { // A PENDING lock is needed before acquiring an EXCLUSIVE lock. if f.lock < LOCK_PENDING { // If we're already RESERVED, we can block indefinitely, - // since only new readers may briefly hold the PENDING lock. + // since only incoming readers may briefly hold the PENDING lock. if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK { return rc } f.lock = LOCK_PENDING } - // We already have PENDING, so we're just waiting for readers to leave. - // If we were RESERVED, we can wait for a little while, before invoking - // the busy handler; we will only do this once. - if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK { + // We are now PENDING, so we're just waiting for readers to leave. + // If we were RESERVED, we can block for a bit before invoking the busy handler. + if rc := osGetExclusiveLock(f.File, reserved /* block */); rc != _OK { return rc } f.lock = LOCK_EXCLUSIVE diff --git a/vfs/os_unix_lock.go b/vfs/os_unix_lock.go index ffa1f5e..2616a0f 100644 --- a/vfs/os_unix_lock.go +++ b/vfs/os_unix_lock.go @@ -32,9 +32,9 @@ func osGetPendingLock(file *os.File, block bool) _ErrorCode { return osWriteLock(file, _PENDING_BYTE, 1, timeout) } -func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode { +func osGetExclusiveLock(file *os.File, block bool) _ErrorCode { var timeout time.Duration - if wait { + if block { timeout = time.Millisecond } // Acquire the EXCLUSIVE lock. diff --git a/vfs/os_windows.go b/vfs/os_windows.go index 7425b55..351e819 100644 --- a/vfs/os_windows.go +++ b/vfs/os_windows.go @@ -38,9 +38,9 @@ func osGetPendingLock(file *os.File, block bool) _ErrorCode { return osWriteLock(file, _PENDING_BYTE, 1, timeout) } -func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode { +func osGetExclusiveLock(file *os.File, block bool) _ErrorCode { var timeout time.Duration - if wait { + if block { timeout = time.Millisecond } diff --git a/vfs/shm.go b/vfs/shm.go index 7b0d4b6..36f55d0 100644 --- a/vfs/shm.go +++ b/vfs/shm.go @@ -6,6 +6,7 @@ import ( "context" "io" "os" + "time" "github.com/ncruces/go-sqlite3/internal/util" "github.com/tetratelabs/wazero/api" @@ -49,6 +50,7 @@ type vfsShm struct { path string regions []*util.MappedRegion readOnly bool + blocking bool } func (s *vfsShm) shmOpen() _ErrorCode { @@ -76,6 +78,13 @@ func (s *vfsShm) shmOpen() _ErrorCode { if s.readOnly { return _READONLY_CANTINIT } + // Do not use a blocking lock here. + // If the lock cannot be obtained immediately, + // it means some other connection is truncating the file. + // And after it has done so, it will not release its lock, + // but only downgrade it to a shared lock. + // So no point in blocking here. + // The call below to obtain the shared DMS lock may use a blocking lock. if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK { return rc } @@ -83,7 +92,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { return _IOERR_SHMOPEN } } - if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK { + if rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond); rc != _OK { return rc } return _OK @@ -150,13 +159,18 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { panic(util.AssertErr()) } + var timeout time.Duration + if s.blocking { + timeout = time.Millisecond + } + switch { case flags&_SHM_UNLOCK != 0: return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n)) case flags&_SHM_SHARED != 0: - return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) + return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout) case flags&_SHM_EXCLUSIVE != 0: - return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) + return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout) default: panic(util.AssertErr()) } @@ -181,3 +195,7 @@ func (s *vfsShm) shmUnmap(delete bool) { s.Close() s.File = nil } + +func (s *vfsShm) shmEnableBlocking(block bool) { + s.blocking = block +} diff --git a/vfs/tests/mptest/testdata/mptest.wasm.bz2 b/vfs/tests/mptest/testdata/mptest.wasm.bz2 index ed2538c..c3d70de 100644 --- a/vfs/tests/mptest/testdata/mptest.wasm.bz2 +++ b/vfs/tests/mptest/testdata/mptest.wasm.bz2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d58c92d45fb60dc2eea461b0e7c1d512cb1dd00deafdfaab3e24b943f714f69 -size 475960 +oid sha256:95b87ae12a3d5635f00fd3014667caaa89094fa36a23ba9a3bfc3561ef226a03 +size 476074 diff --git a/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 b/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 index 210d099..39cae62 100644 --- a/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 +++ b/vfs/tests/speedtest1/testdata/speedtest1.wasm.bz2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90eb053e2a17bd73d8fb17f82c60e595b71018a7880c7b04d7f4b6aae187f5a5 -size 489132 +oid sha256:41eb60af3e4010d1e8dd4011ff9fa02bf299e15e38c2ce6f8b04569cb5aa6579 +size 489289 diff --git a/vfs/vfs.go b/vfs/vfs.go index 983f285..e0453fc 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -243,6 +243,15 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl return _OK } + case _FCNTL_LOCK_TIMEOUT: + if file, ok := file.(FileSharedMemory); ok { + if iface, ok := file.SharedMemory().(interface{ shmEnableBlocking(bool) }); ok { + if i := util.ReadUint32(mod, pArg); i == 0 || i == 1 { + iface.shmEnableBlocking(i != 0) + } + } + } + case _FCNTL_PERSIST_WAL: if file, ok := file.(FilePersistentWAL); ok { if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {