Files
sqlite3/vfs_lock.go
Nuno Cruces 191c1f53df Refactor.
2023-01-25 13:19:38 +00:00

249 lines
6.9 KiB
Go

package sqlite3
import (
"context"
"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
)
type vfsLockState uint32
type vfsLocker interface {
LockState() vfsLockState
LockShared() uint32 // UNLOCKED -> SHARED
LockReserved() uint32 // SHARED -> RESERVED
LockPending() uint32 // SHARED|RESERVED -> PENDING
LockExclusive() uint32 // PENDING -> EXCLUSIVE
DowngradeLock() uint32 // SHARED <- EXCLUSIVE|PENDING|RESERVED
Unlock() uint32 // UNLOCKED <- EXCLUSIVE|PENDING|RESERVED|SHARED
CheckReservedLock() (bool, uint32)
}
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
if assert && (eLock == _NO_LOCK || eLock == _PENDING_LOCK) {
panic(assertErr + " [d4oxww]")
}
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
// If we already have an equal or more restrictive lock, do nothing.
if cLock >= eLock {
return _OK
}
if assert {
switch {
case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
// We never move from unlocked to anything higher than shared lock.
panic(assertErr + " [pfa77m]")
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
// A shared lock is always held when a reserved lock is requested.
panic(assertErr + " [5cfmsp]")
}
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := of.LockState()
// If some other connection has a lock that precludes the requested lock, return BUSY.
if cLock != fLock && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) {
return uint32(BUSY)
}
if eLock == _EXCLUSIVE_LOCK && of.shared > 1 {
// We are trying for an exclusive lock but another connection in this
// same process is still holding a shared lock.
return uint32(BUSY)
}
// If a SHARED lock is requested, and some other connection has a SHARED or RESERVED lock,
// then increment the reference count and return OK.
if eLock == _SHARED_LOCK && (fLock == _SHARED_LOCK || fLock == _RESERVED_LOCK) {
if assert && !(cLock == _NO_LOCK && of.shared > 0) {
panic(assertErr + " [k7coz6]")
}
ptr.SetLock(_SHARED_LOCK)
of.shared++
return _OK
}
// If control gets to this point, then actually go ahead and make
// operating system calls for the specified lock.
switch eLock {
case _SHARED_LOCK:
if assert && !(fLock == _NO_LOCK && of.shared == 0) {
panic(assertErr + " [jsyttq]")
}
if rc := of.LockShared(); rc != _OK {
return uint32(rc)
}
of.shared = 1
ptr.SetLock(_SHARED_LOCK)
return _OK
case _RESERVED_LOCK:
if rc := of.LockReserved(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_RESERVED_LOCK)
return _OK
case _EXCLUSIVE_LOCK:
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if cLock < _PENDING_LOCK {
if rc := of.LockPending(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_PENDING_LOCK)
}
if rc := of.LockExclusive(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_EXCLUSIVE_LOCK)
return _OK
default:
panic(assertErr + " [56ng2l]")
}
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
if assert && (eLock != _NO_LOCK && eLock != _SHARED_LOCK) {
panic(assertErr + " [7i4jw3]")
}
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
// If we don't have a more restrictive lock, do nothing.
if cLock <= eLock {
return _OK
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := of.LockState()
if assert && of.shared <= 0 {
panic(assertErr + " [2bhkwg]")
}
if cLock > _SHARED_LOCK {
if assert && cLock != fLock {
panic(assertErr + " [6pmjqf]")
}
if eLock == _SHARED_LOCK {
if rc := of.DowngradeLock(); 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
// protocol. If this happens, return IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer.
return uint32(IOERR_RDLOCK)
}
ptr.SetLock(_SHARED_LOCK)
return _OK
}
}
if assert && eLock != _NO_LOCK {
panic(assertErr + " [gilo9p]")
}
// Decrement the shared lock counter. Release the file lock
// only when all connections have released the lock.
switch {
case of.shared > 1:
ptr.SetLock(_NO_LOCK)
of.shared--
return _OK
case of.shared == 1:
if rc := of.Unlock(); rc != _OK {
return uint32(IOERR_UNLOCK)
}
ptr.SetLock(_NO_LOCK)
of.shared = 0
return _OK
default:
panic(assertErr + " [50gw51]")
}
}
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
if assert && cLock > _SHARED_LOCK {
panic(assertErr + " [zarygt]")
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
locked, rc := of.CheckReservedLock()
if rc != _OK {
return uint32(IOERR_CHECKRESERVEDLOCK)
}
var res uint32
if locked {
res = 1
}
if ok := mod.Memory().WriteUint32Le(pResOut, res); !ok {
panic(rangeErr)
}
return _OK
}