Use flock.

This commit is contained in:
Nuno Cruces
2023-03-22 03:15:54 +00:00
parent 589ad86f76
commit 8ff0ee752f
9 changed files with 219 additions and 118 deletions

View File

@@ -41,13 +41,14 @@ disable connection pooling by calling
#### Open File Description Locks
On Unix, this module uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
to synchronize access to database files.
POSIX advisory locks, which SQLite uses, are [broken by design](https://www.sqlite.org/src/artifact/90c4fa?ln=1073-1161).
OFD locks are fully compatible with process-associated POSIX advisory locks,
and are supported on Linux and macOS.
As a work around for other Unixes, you can use [`nolock=1`](https://www.sqlite.org/uri.html).
On Linux and macOS, this module uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
to synchronize access to database files.
OFD locks are fully compatible with process-associated POSIX advisory locks.
On other Unixes, this module uses [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
BSD locks may _not_ be compatible with process-associated POSIX advisory locks.
#### Testing

View File

@@ -202,7 +202,6 @@ const (
noFuncErr = errorString("sqlite3: could not find function: ")
binaryErr = errorString("sqlite3: no SQLite binary embed/set/loaded")
timeErr = errorString("sqlite3: invalid time value")
notImplErr = errorString("sqlite3: not implemented")
whenceErr = errorString("sqlite3: invalid whence")
offsetErr = errorString("sqlite3: invalid offset")
)

3
vfs.go
View File

@@ -338,9 +338,6 @@ func vfsSizeHint(ctx context.Context, mod api.Module, pFile, pArg uint32) uint32
file := vfsFile.GetOS(ctx, mod, pFile)
size := memory{mod}.readUint64(pArg)
err := vfsOS.Allocate(file, int64(size))
if err == notImplErr {
return uint32(NOTFOUND)
}
if err != nil {
return uint32(IOERR_TRUNCATE)
}

View File

@@ -1,12 +1,26 @@
package sqlite3
import (
"io"
"os"
"time"
"golang.org/x/sys/unix"
)
const (
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
_F_OFD_SETLK = 90
_F_OFD_SETLKW = 91
_F_OFD_GETLK = 92
_F_OFD_SETLKWTIMEOUT = 93
)
type flocktimeout_t struct {
fl unix.Flock_t
timeout unix.Timespec
}
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
if !fullsync {
return unix.Fsync(int(file.Fd()))
@@ -15,6 +29,14 @@ func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
}
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if size <= off {
return nil
}
// https://stackoverflow.com/a/11497568/867786
store := unix.Fstore_t{
Flags: unix.F_ALLOCATECONTIG,
@@ -24,37 +46,67 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error {
}
// Try to get a continous chunk of disk space.
err := unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
if err != nil {
// OK, perhaps we are too fragmented, allocate non-continuous.
store.Flags = unix.F_ALLOCATEALL
return unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
}
return nil
return file.Truncate(size)
}
func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error {
const F_OFD_GETLK = 92 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
return unix.FcntlFlock(file.Fd(), F_OFD_GETLK, lock)
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return IOERR_UNLOCK
}
return _OK
}
func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error {
const F_OFD_SETLK = 90 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
return unix.FcntlFlock(file.Fd(), F_OFD_SETLK, &lock)
}
func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error {
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
lock := flocktimeout_t{fl: unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}}
var err error
if timeout == 0 {
return vfsOS.fcntlSetLock(file, lock)
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
} else {
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
}
const F_OFD_SETLKWTIMEOUT = 93 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
flocktimeout := &struct {
unix.Flock_t
unix.Timespec
}{
Flock_t: lock,
Timespec: unix.NsecToTimespec(int64(timeout / time.Nanosecond)),
}
return unix.FcntlFlock(file.Fd(), F_OFD_SETLKWTIMEOUT, &flocktimeout.Flock_t)
return vfsOS.lockErrorCode(err, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
lock := flocktimeout_t{fl: unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}}
var err error
if timeout == 0 {
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
} else {
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
}
return vfsOS.lockErrorCode(err, IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), _F_OFD_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

View File

@@ -9,7 +9,6 @@ import (
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
if dataonly {
//lint:ignore SA1019 OK on linux
_, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0)
if err != 0 {
return err
@@ -26,24 +25,68 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error {
return unix.Fallocate(int(file.Fd()), 0, 0, size)
}
func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error {
return unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, lock)
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return IOERR_UNLOCK
}
return _OK
}
func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error {
return unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
}
func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error {
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
var err error
for {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
return err
break
}
if timeout < time.Millisecond {
return err
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
lock := unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}
var err error
for {
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, IOERR_RDLOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

80
vfs_os_other.go Normal file
View File

@@ -0,0 +1,80 @@
//go:build !windows && !linux && !darwin
package sqlite3
import (
"io"
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
return file.Sync()
}
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if size <= off {
return nil
}
return file.Truncate(size)
}
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
if start == 0 && len == 0 {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return IOERR_UNLOCK
}
}
return _OK
}
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
var err error
for {
err = unix.Flock(int(file.Fd()), unix.LOCK_SH|unix.LOCK_NB)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
var err error
for {
err = unix.Flock(int(file.Fd()), unix.LOCK_EX|unix.LOCK_NB)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, IOERR_RDLOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

View File

@@ -1,30 +0,0 @@
//go:build !windows && !linux && !darwin
package sqlite3
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
return file.Sync()
}
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
return notImplErr
}
func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error {
return notImplErr
}
func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error {
return notImplErr
}
func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error {
return notImplErr
}

View File

@@ -55,47 +55,6 @@ func (vfsOSMethods) ReleaseLock(file *os.File, _ vfsLockState) xErrorCode {
return vfsOS.unlock(file, 0, 0)
}
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
err := vfsOS.fcntlSetLock(file, unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return IOERR_UNLOCK
}
return _OK
}
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,
}, timeout), IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
// TODO: implement timeouts.
return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}, timeout), IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if vfsOS.fcntlGetLock(file, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}
func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode {
if err == nil {
return _OK

View File

@@ -57,10 +57,10 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error {
if err != nil {
return err
}
if size > off {
return file.Truncate(size)
if size <= off {
return nil
}
return nil
return file.Truncate(size)
}
func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode {