Files
sqlite3/vfs_lock.go

276 lines
7.2 KiB
Go
Raw Permalink Normal View History

2023-01-24 16:59:51 +00:00
package sqlite3
import (
"context"
2023-01-26 00:05:52 +00:00
"os"
2023-02-07 03:11:59 +00:00
"sync"
2023-01-24 16:59:51 +00:00
"github.com/tetratelabs/wazero/api"
)
const (
// No locks are held on the database.
// The database may be neither read nor written.
// Any internally cached data is considered suspect and subject to
// verification against the database file before being used.
// Other processes can read or write the database as their own locking
// states permit.
// This is the default state.
_NO_LOCK = 0
// The database may be read but not written.
// Any number of processes can hold SHARED locks at the same time,
// hence there can be many simultaneous readers.
// But no other thread or process is allowed to write to the database file
// while one or more SHARED locks are active.
_SHARED_LOCK = 1
// A RESERVED lock means that the process is planning on writing to the
// database file at some point in the future but that it is currently just
// reading from the file.
// Only a single RESERVED lock may be active at one time,
// though multiple SHARED locks can coexist with a single RESERVED lock.
// RESERVED differs from PENDING in that new SHARED locks can be acquired
// while there is a RESERVED lock.
_RESERVED_LOCK = 2
// A PENDING lock means that the process holding the lock wants to write to
// the database as soon as possible and is just waiting on all current
// SHARED locks to clear so that it can get an EXCLUSIVE lock.
// No new SHARED locks are permitted against the database if a PENDING lock
// is active, though existing SHARED locks are allowed to continue.
_PENDING_LOCK = 3
// An EXCLUSIVE lock is needed in order to write to the database file.
// Only one EXCLUSIVE lock is allowed on the file and no other locks of any
// kind are allowed to coexist with an EXCLUSIVE lock.
// In order to maximize concurrency, SQLite works to minimize the amount of
// time that EXCLUSIVE locks are held.
_EXCLUSIVE_LOCK = 4
_PENDING_BYTE = 0x40000000
_RESERVED_BYTE = (_PENDING_BYTE + 1)
_SHARED_FIRST = (_PENDING_BYTE + 2)
_SHARED_SIZE = 510
)
2023-02-07 03:11:59 +00:00
type vfsLockState uint32
2023-01-24 16:59:51 +00:00
2023-01-26 00:05:52 +00:00
type vfsFileLocker struct {
2023-02-07 03:11:59 +00:00
sync.Mutex
file *os.File
state vfsLockState
shared int
2023-01-26 00:05:52 +00:00
}
2023-01-25 13:19:38 +00:00
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
2023-02-13 13:53:32 +00:00
// Argument check. SQLite never explicitly requests a pendig lock.
2023-02-07 03:11:59 +00:00
if eLock != _SHARED_LOCK && eLock != _RESERVED_LOCK && eLock != _EXCLUSIVE_LOCK {
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
2023-02-07 03:11:59 +00:00
switch {
2023-02-13 13:53:32 +00:00
case cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK:
// Connection state check.
panic(assertErr())
2023-02-07 03:11:59 +00:00
case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
// We never move from unlocked to anything higher than a shared lock.
panic(assertErr())
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
// A shared lock is always held when a reserved lock is requested.
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
2023-02-13 13:53:32 +00:00
// If we already have an equal or more restrictive lock, do nothing.
if cLock >= eLock {
return _OK
}
2023-02-07 03:11:59 +00:00
fLock := ptr.Locker()
fLock.Lock()
defer fLock.Unlock()
2023-01-24 16:59:51 +00:00
2023-02-13 13:53:32 +00:00
// File state check.
switch {
case fLock.state < _NO_LOCK || fLock.state > _EXCLUSIVE_LOCK:
panic(assertErr())
case fLock.state == _NO_LOCK && fLock.shared != 0:
panic(assertErr())
case fLock.state == _EXCLUSIVE_LOCK && fLock.shared != 1:
panic(assertErr())
case fLock.state != _NO_LOCK && fLock.shared <= 0:
panic(assertErr())
case fLock.state < cLock:
panic(assertErr())
}
2023-01-24 16:59:51 +00:00
// If some other connection has a lock that precludes the requested lock, return BUSY.
2023-02-07 03:11:59 +00:00
if cLock != fLock.state && (eLock > _SHARED_LOCK || fLock.state >= _PENDING_LOCK) {
2023-01-24 16:59:51 +00:00
return uint32(BUSY)
}
switch eLock {
case _SHARED_LOCK:
2023-02-13 13:53:32 +00:00
// Test the PENDING lock before acquiring a new SHARED lock.
if locked, _ := fLock.CheckPending(); locked {
return uint32(BUSY)
}
// If some other connection has a SHARED or RESERVED lock,
// increment the reference count and return OK.
if fLock.state == _SHARED_LOCK || fLock.state == _RESERVED_LOCK {
ptr.SetLock(_SHARED_LOCK)
fLock.shared++
return _OK
}
// Must be unlocked to get SHARED.
if fLock.state != _NO_LOCK {
2023-02-07 03:11:59 +00:00
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
2023-02-07 03:11:59 +00:00
if rc := fLock.GetShared(); rc != _OK {
2023-01-24 16:59:51 +00:00
return uint32(rc)
}
ptr.SetLock(_SHARED_LOCK)
2023-02-07 15:04:42 +00:00
fLock.state = _SHARED_LOCK
2023-02-07 03:11:59 +00:00
fLock.shared = 1
2023-01-24 16:59:51 +00:00
return _OK
case _RESERVED_LOCK:
2023-02-13 13:53:32 +00:00
// Must be SHARED to get RESERVED.
if fLock.state != _SHARED_LOCK {
2023-02-07 03:11:59 +00:00
panic(assertErr())
}
if rc := fLock.GetReserved(); rc != _OK {
2023-01-24 16:59:51 +00:00
return uint32(rc)
}
ptr.SetLock(_RESERVED_LOCK)
2023-02-07 15:04:42 +00:00
fLock.state = _RESERVED_LOCK
2023-01-24 16:59:51 +00:00
return _OK
case _EXCLUSIVE_LOCK:
2023-02-13 13:53:32 +00:00
// Must be SHARED, PENDING or RESERVED to get EXCLUSIVE.
if fLock.state <= _NO_LOCK || fLock.state >= _EXCLUSIVE_LOCK {
2023-02-07 03:11:59 +00:00
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
2023-02-07 15:04:42 +00:00
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if fLock.state == _RESERVED_LOCK {
if rc := fLock.GetPending(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_PENDING_LOCK)
fLock.state = _PENDING_LOCK
}
// We are trying for an EXCLUSIVE lock but another connection is still holding a shared lock.
if fLock.shared > 1 {
return uint32(BUSY)
}
2023-02-07 03:11:59 +00:00
if rc := fLock.GetExclusive(); rc != _OK {
2023-01-24 16:59:51 +00:00
return uint32(rc)
}
ptr.SetLock(_EXCLUSIVE_LOCK)
2023-02-07 15:04:42 +00:00
fLock.state = _EXCLUSIVE_LOCK
2023-01-24 16:59:51 +00:00
return _OK
default:
2023-02-07 03:11:59 +00:00
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
}
2023-01-25 13:19:38 +00:00
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
2023-02-13 13:53:32 +00:00
// Argument check.
2023-02-07 03:11:59 +00:00
if eLock != _NO_LOCK && eLock != _SHARED_LOCK {
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
2023-02-13 13:53:32 +00:00
// Connection state check.
if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK {
panic(assertErr())
}
2023-01-24 16:59:51 +00:00
// If we don't have a more restrictive lock, do nothing.
if cLock <= eLock {
return _OK
}
2023-02-07 03:11:59 +00:00
fLock := ptr.Locker()
fLock.Lock()
defer fLock.Unlock()
2023-01-24 16:59:51 +00:00
2023-02-13 13:53:32 +00:00
// File state check.
switch {
case fLock.state <= _NO_LOCK || fLock.state > _EXCLUSIVE_LOCK:
panic(assertErr())
case fLock.state == _EXCLUSIVE_LOCK && fLock.shared != 1:
panic(assertErr())
case fLock.shared <= 0:
panic(assertErr())
case fLock.state < cLock:
2023-02-07 03:11:59 +00:00
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
2023-02-13 13:53:32 +00:00
2023-01-24 16:59:51 +00:00
if cLock > _SHARED_LOCK {
2023-02-13 13:53:32 +00:00
// The connection must own the lock to release it.
2023-02-07 03:11:59 +00:00
if cLock != fLock.state {
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
if eLock == _SHARED_LOCK {
2023-02-07 03:11:59 +00:00
if rc := fLock.Downgrade(); rc != _OK {
2023-01-27 01:45:38 +00:00
return uint32(rc)
2023-01-24 16:59:51 +00:00
}
ptr.SetLock(_SHARED_LOCK)
2023-02-07 15:04:42 +00:00
fLock.state = _SHARED_LOCK
2023-01-24 16:59:51 +00:00
return _OK
}
}
2023-02-13 13:53:32 +00:00
// If we get here, make sure we're dropping all locks.
2023-02-07 03:11:59 +00:00
if eLock != _NO_LOCK {
panic(assertErr())
2023-01-24 16:59:51 +00:00
}
2023-02-07 03:11:59 +00:00
2023-02-07 15:04:42 +00:00
// Release the connection lock and decrement the shared lock counter.
// Release the file lock only when all connections have released the lock.
ptr.SetLock(_NO_LOCK)
if fLock.shared--; fLock.shared == 0 {
fLock.state = _NO_LOCK
return uint32(fLock.Release())
2023-01-24 16:59:51 +00:00
}
2023-02-07 15:04:42 +00:00
return _OK
2023-01-24 16:59:51 +00:00
}
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
2023-01-25 00:33:01 +00:00
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
2023-02-07 03:11:59 +00:00
if cLock > _SHARED_LOCK {
panic(assertErr())
2023-01-25 00:33:01 +00:00
}
2023-02-07 03:11:59 +00:00
fLock := ptr.Locker()
fLock.Lock()
defer fLock.Unlock()
2023-01-25 00:33:01 +00:00
2023-02-07 15:04:42 +00:00
if fLock.state >= _RESERVED_LOCK {
memory{mod}.writeUint32(pResOut, 1)
return _OK
2023-01-25 00:33:01 +00:00
}
2023-02-07 15:04:42 +00:00
locked, rc := fLock.CheckReserved()
2023-01-25 00:33:01 +00:00
var res uint32
if locked {
res = 1
}
2023-01-26 14:52:38 +00:00
memory{mod}.writeUint32(pResOut, res)
2023-02-07 15:04:42 +00:00
return uint32(rc)
2023-01-24 16:59:51 +00:00
}