diff --git a/vfs_file.go b/vfs_file.go index 0df6386..f3e0df1 100644 --- a/vfs_file.go +++ b/vfs_file.go @@ -3,6 +3,7 @@ package sqlite3 import ( "context" "os" + "time" "github.com/tetratelabs/wazero/api" ) @@ -61,3 +62,8 @@ func (vfsFileMethods) SetLock(ctx context.Context, mod api.Module, pFile uint32, mem := memory{mod} mem.writeUint8(pFile+vfsFileLockOffset, uint8(lock)) } + +func (vfsFileMethods) GetLockTimeout(ctx context.Context, mod api.Module, pFile uint32) time.Duration { + mem := memory{mod} + return time.Duration(mem.readUint32(pFile+vfsFileLockTimeoutOffset)) * time.Millisecond +} diff --git a/vfs_lock.go b/vfs_lock.go index 7648a6e..8338372 100644 --- a/vfs_lock.go +++ b/vfs_lock.go @@ -3,6 +3,7 @@ package sqlite3 import ( "context" "os" + "time" "github.com/tetratelabs/wazero/api" ) @@ -63,6 +64,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta file := vfsFile.GetOS(ctx, mod, pFile) cLock := vfsFile.GetLock(ctx, mod, pFile) + timeout := vfsFile.GetLockTimeout(ctx, mod, pFile) switch { case cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK: @@ -91,7 +93,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta if locked, _ := vfsOS.CheckPendingLock(file); locked { return uint32(BUSY) } - if rc := vfsOS.GetSharedLock(file); rc != _OK { + if rc := vfsOS.GetSharedLock(file, timeout); rc != _OK { return uint32(rc) } vfsFile.SetLock(ctx, mod, pFile, _SHARED_LOCK) @@ -102,7 +104,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta if cLock != _SHARED_LOCK { panic(assertErr()) } - if rc := vfsOS.GetReservedLock(file); rc != _OK { + if rc := vfsOS.GetReservedLock(file, timeout); rc != _OK { return uint32(rc) } vfsFile.SetLock(ctx, mod, pFile, _RESERVED_LOCK) @@ -120,7 +122,7 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta } vfsFile.SetLock(ctx, mod, pFile, _PENDING_LOCK) } - if rc := vfsOS.GetExclusiveLock(file); rc != _OK { + if rc := vfsOS.GetExclusiveLock(file, timeout); rc != _OK { return uint32(rc) } vfsFile.SetLock(ctx, mod, pFile, _EXCLUSIVE_LOCK) @@ -180,19 +182,19 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui return uint32(rc) } -func (vfsOSMethods) GetSharedLock(file *os.File) xErrorCode { +func (vfsOSMethods) GetSharedLock(file *os.File, timeout time.Duration) xErrorCode { // Acquire the SHARED lock. - return vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE) + return vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) } -func (vfsOSMethods) GetReservedLock(file *os.File) xErrorCode { +func (vfsOSMethods) GetReservedLock(file *os.File, timeout time.Duration) xErrorCode { // Acquire the RESERVED lock. - return vfsOS.writeLock(file, _RESERVED_BYTE, 1) + return vfsOS.writeLock(file, _RESERVED_BYTE, 1, timeout) } func (vfsOSMethods) GetPendingLock(file *os.File) xErrorCode { // Acquire the PENDING lock. - return vfsOS.writeLock(file, _PENDING_BYTE, 1) + return vfsOS.writeLock(file, _PENDING_BYTE, 1, 0) } func (vfsOSMethods) CheckReservedLock(file *os.File) (bool, xErrorCode) { diff --git a/vfs_os_darwin.go b/vfs_os_darwin.go index eb7410f..84b2459 100644 --- a/vfs_os_darwin.go +++ b/vfs_os_darwin.go @@ -43,7 +43,7 @@ func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error { return unix.FcntlFlock(file.Fd(), F_OFD_SETLK, &lock) } -func (vfsOSMethods) fcntlSetLockTimeout(timeout time.Duration, file *os.File, lock unix.Flock_t) error { +func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error { if timeout == 0 { return vfsOS.fcntlSetLock(file, lock) } diff --git a/vfs_os_linux.go b/vfs_os_linux.go index a82ddc8..78938e3 100644 --- a/vfs_os_linux.go +++ b/vfs_os_linux.go @@ -34,6 +34,16 @@ func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error { return unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) } -func (vfsOSMethods) fcntlSetLockTimeout(timeout time.Duration, file *os.File, lock unix.Flock_t) error { - return vfsOS.fcntlSetLock(file, lock) +func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error { + for { + err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) + if errno, _ := err.(unix.Errno); errno != unix.EAGAIN { + return err + } + if timeout < time.Millisecond { + return err + } + timeout -= time.Millisecond + time.Sleep(time.Millisecond) + } } diff --git a/vfs_os_posix.go b/vfs_os_posix.go index e9abe40..c3deac9 100644 --- a/vfs_os_posix.go +++ b/vfs_os_posix.go @@ -25,6 +25,6 @@ func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error { return notImplErr } -func (vfsOSMethods) fcntlSetLockTimeout(timeout time.Duration, file *os.File, lock unix.Flock_t) error { +func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error { return notImplErr } diff --git a/vfs_os_unix.go b/vfs_os_unix.go index 7049d73..8d9aba9 100644 --- a/vfs_os_unix.go +++ b/vfs_os_unix.go @@ -5,6 +5,7 @@ package sqlite3 import ( "io/fs" "os" + "time" "golang.org/x/sys/unix" ) @@ -24,15 +25,19 @@ func (vfsOSMethods) Access(path string, flags _AccessFlag) error { return unix.Access(path, access) } -func (vfsOSMethods) GetExclusiveLock(file *os.File) xErrorCode { +func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode { + if timeout == 0 { + timeout = time.Millisecond + } + // Acquire the EXCLUSIVE lock. - return vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE) + return vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) } func (vfsOSMethods) DowngradeLock(file *os.File, state vfsLockState) xErrorCode { if state >= _EXCLUSIVE_LOCK { // Downgrade to a SHARED lock. - if rc := vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE); rc != _OK { + if rc := vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { // In theory, the downgrade to a SHARED cannot fail because another // process is holding an incompatible lock. If it does, this // indicates that the other process is not following the locking @@ -62,21 +67,21 @@ func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode { return _OK } -func (vfsOSMethods) readLock(file *os.File, start, len int64) xErrorCode { - return vfsOS.lockErrorCode(vfsOS.fcntlSetLock(file, unix.Flock_t{ +func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { + return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{ Type: unix.F_RDLCK, Start: start, Len: len, - }), IOERR_RDLOCK) + }, timeout), IOERR_RDLOCK) } -func (vfsOSMethods) writeLock(file *os.File, start, len int64) xErrorCode { +func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode { // TODO: implement timeouts. - return vfsOS.lockErrorCode(vfsOS.fcntlSetLock(file, unix.Flock_t{ + return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{ Type: unix.F_WRLCK, Start: start, Len: len, - }), IOERR_LOCK) + }, timeout), IOERR_LOCK) } func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) { diff --git a/vfs_os_windows.go b/vfs_os_windows.go index 5deb437..a2be3ce 100644 --- a/vfs_os_windows.go +++ b/vfs_os_windows.go @@ -5,6 +5,7 @@ import ( "io/fs" "os" "syscall" + "time" "golang.org/x/sys/windows" ) @@ -62,16 +63,20 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error { return nil } -func (vfsOSMethods) GetExclusiveLock(file *os.File) xErrorCode { +func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode { + if timeout == 0 { + timeout = time.Millisecond + } + // Release the SHARED lock. vfsOS.unlock(file, _SHARED_FIRST, _SHARED_SIZE) // Acquire the EXCLUSIVE lock. - rc := vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE) + rc := vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) // Reacquire the SHARED lock. if rc != _OK { - vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE) + vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) } return rc } @@ -82,7 +87,7 @@ func (vfsOSMethods) DowngradeLock(file *os.File, state vfsLockState) xErrorCode vfsOS.unlock(file, _SHARED_FIRST, _SHARED_SIZE) // Reacquire the SHARED lock. - if rc := vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE); rc != _OK { + if rc := vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { // This should never happen. // We should always be able to reacquire the read lock. return IOERR_RDLOCK @@ -125,25 +130,37 @@ func (vfsOSMethods) unlock(file *os.File, start, len uint32) xErrorCode { return _OK } -func (vfsOSMethods) readLock(file *os.File, start, len uint32) xErrorCode { - return vfsOS.lockErrorCode(windows.LockFileEx(windows.Handle(file.Fd()), - windows.LOCKFILE_FAIL_IMMEDIATELY, - 0, len, 0, &windows.Overlapped{Offset: start}), - IOERR_RDLOCK) +func (vfsOSMethods) lock(file *os.File, flags, start, len uint32, timeout time.Duration, def xErrorCode) xErrorCode { + for { + err := windows.LockFileEx(windows.Handle(file.Fd()), flags, + 0, len, 0, &windows.Overlapped{Offset: start}) + if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION { + return vfsOS.lockErrorCode(err, def) + } + if timeout < time.Millisecond { + return vfsOS.lockErrorCode(err, def) + } + timeout -= time.Millisecond + time.Sleep(time.Millisecond) + } } -func (vfsOSMethods) writeLock(file *os.File, start, len uint32) xErrorCode { - return vfsOS.lockErrorCode(windows.LockFileEx(windows.Handle(file.Fd()), +func (vfsOSMethods) readLock(file *os.File, start, len uint32, timeout time.Duration) xErrorCode { + return vfsOS.lock(file, + windows.LOCKFILE_FAIL_IMMEDIATELY, + start, len, timeout, IOERR_RDLOCK) +} + +func (vfsOSMethods) writeLock(file *os.File, start, len uint32, timeout time.Duration) xErrorCode { + return vfsOS.lock(file, windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK, - 0, len, 0, &windows.Overlapped{Offset: start}), - IOERR_LOCK) + start, len, timeout, IOERR_LOCK) } func (vfsOSMethods) checkLock(file *os.File, start, len uint32) (bool, xErrorCode) { - rc := vfsOS.lockErrorCode(windows.LockFileEx(windows.Handle(file.Fd()), + rc := vfsOS.lock(file, windows.LOCKFILE_FAIL_IMMEDIATELY, - 0, len, 0, &windows.Overlapped{Offset: start}), - IOERR_CHECKRESERVEDLOCK) + start, len, 0, IOERR_CHECKRESERVEDLOCK) if rc == xErrorCode(BUSY) { return true, _OK }