mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Improve Wal locking on BSD (#204)
This commit is contained in:
@@ -74,7 +74,7 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3
|
|||||||
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
||||||
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
|
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
|
||||||
|
|
||||||
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on
|
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) on
|
||||||
Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64),
|
Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64),
|
||||||
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
|
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
|
||||||
DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64).
|
DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64).
|
||||||
|
|||||||
@@ -48,11 +48,6 @@ On Unix, this package may use `mmap` to implement
|
|||||||
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
|
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
|
||||||
like SQLite.
|
like SQLite.
|
||||||
|
|
||||||
With [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2)
|
|
||||||
a WAL database can only be accessed by a single proccess.
|
|
||||||
Other processes that attempt to access a database locked with BSD locks,
|
|
||||||
will fail with the [`SQLITE_PROTOCOL`](https://sqlite.org/rescode.html#protocol) error code.
|
|
||||||
|
|
||||||
On Windows, this package may use `MapViewOfFile`, like SQLite.
|
On Windows, this package may use `MapViewOfFile`, like SQLite.
|
||||||
|
|
||||||
You can also opt into a cross-platform, in-process, memory sharing implementation
|
You can also opt into a cross-platform, in-process, memory sharing implementation
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func osGetSharedLock(file *os.File) _ErrorCode {
|
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||||
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
return osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func osGetReservedLock(file *os.File) _ErrorCode {
|
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||||
rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
|
rc := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
|
||||||
if rc == _BUSY {
|
if rc == _BUSY {
|
||||||
// The documentation states that a lock is upgraded by
|
// The documentation states that a lock is upgraded by
|
||||||
// releasing the previous lock, then acquiring the new lock.
|
// releasing the previous lock, then acquiring the new lock.
|
||||||
@@ -37,7 +37,7 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||||
rc := osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
rc := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||||
if rc == _BUSY {
|
if rc == _BUSY {
|
||||||
// The documentation states that a lock is downgraded by
|
// The documentation states that a lock is downgraded by
|
||||||
// releasing the previous lock then acquiring the new lock.
|
// releasing the previous lock then acquiring the new lock.
|
||||||
@@ -66,7 +66,36 @@ func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
|||||||
return lock == unix.F_WRLCK, rc
|
return lock == unix.F_WRLCK, rc
|
||||||
}
|
}
|
||||||
|
|
||||||
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
|
func osFlock(file *os.File, how int, def _ErrorCode) _ErrorCode {
|
||||||
err := unix.Flock(int(file.Fd()), how)
|
err := unix.Flock(int(file.Fd()), how)
|
||||||
return osLockErrorCode(err, def)
|
return osLockErrorCode(err, def)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func osReadLock(file *os.File, start, len int64) _ErrorCode {
|
||||||
|
return osLock(file, unix.F_RDLCK, start, len, _IOERR_RDLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osWriteLock(file *os.File, start, len int64) _ErrorCode {
|
||||||
|
return osLock(file, unix.F_WRLCK, start, len, _IOERR_LOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCode {
|
||||||
|
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
|
||||||
|
Type: typ,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
})
|
||||||
|
return osLockErrorCode(err, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||||
|
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
|
||||||
|
Type: unix.F_UNLCK,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ package vfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -71,23 +73,16 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
|||||||
return _OK
|
return _OK
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always open file read-write, as it will be shared.
|
|
||||||
f, err := os.OpenFile(s.path,
|
|
||||||
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return _CANTOPEN
|
|
||||||
}
|
|
||||||
// Closes file if it's not nil.
|
|
||||||
defer func() { f.Close() }()
|
|
||||||
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return _IOERR_FSTAT
|
|
||||||
}
|
|
||||||
|
|
||||||
vfsShmListMtx.Lock()
|
vfsShmListMtx.Lock()
|
||||||
defer vfsShmListMtx.Unlock()
|
defer vfsShmListMtx.Unlock()
|
||||||
|
|
||||||
|
// Stat file without opening it.
|
||||||
|
// Closing it would release all POSIX locks on it.
|
||||||
|
fi, err := os.Stat(s.path)
|
||||||
|
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return _IOERR_FSTAT
|
||||||
|
}
|
||||||
|
|
||||||
// Find a shared file, increase the reference count.
|
// Find a shared file, increase the reference count.
|
||||||
for _, g := range vfsShmList {
|
for _, g := range vfsShmList {
|
||||||
if g != nil && os.SameFile(fi, g.info) {
|
if g != nil && os.SameFile(fi, g.info) {
|
||||||
@@ -97,13 +92,35 @@ func (s *vfsShm) shmOpen() _ErrorCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock and truncate the file.
|
// Always open file read-write, as it will be shared.
|
||||||
// The lock is only released by closing the file.
|
f, err := os.OpenFile(s.path,
|
||||||
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK {
|
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return _CANTOPEN
|
||||||
|
}
|
||||||
|
// Closes file if it's not nil.
|
||||||
|
defer func() { f.Close() }()
|
||||||
|
|
||||||
|
// Dead man's switch.
|
||||||
|
if lock, rc := osTestLock(f, _SHM_DMS, 1); rc != _OK {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
} else if lock == unix.F_WRLCK {
|
||||||
|
return _BUSY
|
||||||
|
} else if lock == unix.F_UNLCK {
|
||||||
|
if rc := osWriteLock(f, _SHM_DMS, 1); rc != _OK {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
if err := f.Truncate(0); err != nil {
|
||||||
|
return _IOERR_SHMOPEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rc := osReadLock(f, _SHM_DMS, 1); rc != _OK {
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
if err := f.Truncate(0); err != nil {
|
|
||||||
return _IOERR_SHMOPEN
|
fi, err = f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_FSTAT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the new shared file.
|
// Add the new shared file.
|
||||||
@@ -157,7 +174,30 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
|||||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
return s.shmMemLock(offset, n, flags)
|
|
||||||
|
// Check if we could obtain/release the lock locally.
|
||||||
|
rc := s.shmMemLock(offset, n, flags)
|
||||||
|
if rc != _OK {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain/release the appropriate file lock.
|
||||||
|
switch {
|
||||||
|
case flags&_SHM_UNLOCK != 0:
|
||||||
|
return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||||
|
case flags&_SHM_SHARED != 0:
|
||||||
|
rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
rc = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the local lock.
|
||||||
|
if rc != _OK {
|
||||||
|
s.shmMemLock(offset, n, flags^(_SHM_UNLOCK|_SHM_LOCK))
|
||||||
|
}
|
||||||
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *vfsShm) shmUnmap(delete bool) {
|
func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
|
|||||||
Reference in New Issue
Block a user