Unix locks.

This commit is contained in:
Nuno Cruces
2023-02-07 03:11:59 +00:00
parent a66e454703
commit 89a8ebecc8
12 changed files with 250 additions and 229 deletions

View File

@@ -9,7 +9,6 @@ const (
_MAX_PATHNAME = 512
assert = true
ptrlen = 4
)

View File

@@ -1,6 +1,7 @@
package sqlite3
import (
"runtime"
"strconv"
"strings"
)
@@ -42,5 +43,12 @@ const (
noNulErr = errorString("sqlite3: missing NUL terminator")
noGlobalErr = errorString("sqlite3: could not find global: ")
noFuncErr = errorString("sqlite3: could not find function: ")
assertErr = errorString("sqlite3: assertion failed")
)
func assertErr() errorString {
msg := "sqlite3: assertion failed"
if _, file, line, ok := runtime.Caller(1); ok {
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
}
return errorString(msg)
}

3
init_test.go Normal file
View File

@@ -0,0 +1,3 @@
package sqlite3_test
import _ "github.com/ncruces/go-sqlite3/embed"

75
tests/db_test.go Normal file
View File

@@ -0,0 +1,75 @@
package tests
import (
"os"
"path/filepath"
"testing"
"github.com/ncruces/go-sqlite3"
_ "github.com/ncruces/go-sqlite3/embed"
)
func TestDB_memory(t *testing.T) {
testDB(t, ":memory:")
}
func TestDB_file(t *testing.T) {
dir, err := os.MkdirTemp("", "sqlite3-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
testDB(t, filepath.Join(dir, "test.db"))
}
func testDB(t *testing.T, name string) {
db, err := sqlite3.Open(name)
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
if err != nil {
t.Fatal(err)
}
err = db.Exec(`INSERT INTO users(id, name) VALUES(0, 'go'), (1, 'zig'), (2, 'whatever')`)
if err != nil {
t.Fatal(err)
}
stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
if err != nil {
t.Fatal(err)
}
row := 0
ids := []int{0, 1, 2}
names := []string{"go", "zig", "whatever"}
for ; stmt.Step(); row++ {
if ids[row] != stmt.ColumnInt(0) {
t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[row])
}
if names[row] != stmt.ColumnText(1) {
t.Errorf("got %q, want %q", stmt.ColumnText(1), names[row])
}
}
if err := stmt.Err(); err != nil {
t.Fatal(err)
}
if row != 3 {
t.Errorf("got %d rows, want %d", row, len(ids))
}
err = stmt.Close()
if err != nil {
t.Fatal(err)
}
err = db.Close()
if err != nil {
t.Fatal(err)
}
}

25
tests/dir_test.go Normal file
View File

@@ -0,0 +1,25 @@
package tests
import (
"errors"
"testing"
"github.com/ncruces/go-sqlite3"
)
func TestDir(t *testing.T) {
_, err := sqlite3.Open(".")
if err == nil {
t.Fatal("want error")
}
var serr *sqlite3.Error
if !errors.As(err, &serr) {
t.Fatal("want sqlite3.Error")
}
if serr.Code != sqlite3.CANTOPEN {
t.Error("want sqlite3.CANTOPEN")
}
if got := err.Error(); got != "sqlite3: unable to open database file" {
t.Error("got message: ", got)
}
}

View File

@@ -1,9 +1,9 @@
package sqlite3_test
package tests
import (
"errors"
"os"
"path/filepath"
"runtime"
"testing"
"time"
@@ -13,72 +13,11 @@ import (
_ "github.com/ncruces/go-sqlite3/embed"
)
func TestDB_memory(t *testing.T) {
testDB(t, ":memory:")
}
func TestDB_file(t *testing.T) {
dir, err := os.MkdirTemp("", "sqlite3-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
testDB(t, filepath.Join(dir, "test.db"))
}
func testDB(t *testing.T, name string) {
db, err := sqlite3.Open(name)
if err != nil {
t.Fatal(err)
}
defer db.Close()
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
if err != nil {
t.Fatal(err)
func TestParallel(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip()
}
err = db.Exec(`INSERT INTO users(id, name) VALUES(0, 'go'), (1, 'zig'), (2, 'whatever')`)
if err != nil {
t.Fatal(err)
}
stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
if err != nil {
t.Fatal(err)
}
row := 0
ids := []int{0, 1, 2}
names := []string{"go", "zig", "whatever"}
for ; stmt.Step(); row++ {
if ids[row] != stmt.ColumnInt(0) {
t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[row])
}
if names[row] != stmt.ColumnText(1) {
t.Errorf("got %q, want %q", stmt.ColumnText(1), names[row])
}
}
if err := stmt.Err(); err != nil {
t.Fatal(err)
}
if row != 3 {
t.Errorf("got %d rows, want %d", row, len(ids))
}
err = stmt.Close()
if err != nil {
t.Fatal(err)
}
err = db.Close()
if err != nil {
t.Fatal(err)
}
}
func TestDB_parallel(t *testing.T) {
dir, err := os.MkdirTemp("", "sqlite3-")
if err != nil {
t.Fatal(err)
@@ -172,20 +111,3 @@ func TestDB_parallel(t *testing.T) {
t.Fatal(err)
}
}
func TestOpen_dir(t *testing.T) {
_, err := sqlite3.Open(".")
if err == nil {
t.Fatal("want error")
}
var serr *sqlite3.Error
if !errors.As(err, &serr) {
t.Fatal("want sqlite3.Error")
}
if serr.Code != sqlite3.CANTOPEN {
t.Error("want sqlite3.CANTOPEN")
}
if got := err.Error(); got != "sqlite3: unable to open database file" {
t.Error("got message: ", got)
}
}

4
vfs.go
View File

@@ -292,8 +292,8 @@ func vfsSync(ctx context.Context, mod api.Module, pFile, flags uint32) uint32 {
}
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) uint32 {
// This uses [file.Seek] because we don't care about the offset for reading/writing.
// But consider using [file.Stat] instead (as other VFSes do).
// This uses [os.File.Seek] because we don't care about the offset for reading/writing.
// But consider using [os.File.Stat] instead (as other VFSes do).
file := vfsFilePtr{mod, pFile}.OSFile()
off, err := file.Seek(0, io.SeekEnd)

View File

@@ -8,12 +8,10 @@ import (
)
type vfsOpenFile struct {
file *os.File
info os.FileInfo
nref int
shared int
vfsLocker
file *os.File
info os.FileInfo
nref int
locker vfsFileLocker
}
var (
@@ -38,11 +36,10 @@ func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 {
}
of := &vfsOpenFile{
file: file,
info: info,
nref: 1,
vfsLocker: &vfsFileLocker{file, _NO_LOCK},
file: file,
info: info,
nref: 1,
locker: vfsFileLocker{file: file},
}
// Find an empty slot.
@@ -84,6 +81,13 @@ func (p vfsFilePtr) OSFile() *os.File {
return vfsOpenFiles[id].file
}
func (p vfsFilePtr) Locker() *vfsFileLocker {
id := p.ID()
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
return &vfsOpenFiles[id].locker
}
func (p vfsFilePtr) ID() uint32 {
return memory{p}.readUint32(p.ptr + ptrlen)
}

View File

@@ -3,6 +3,7 @@ package sqlite3
import (
"context"
"os"
"sync"
"github.com/tetratelabs/wazero/api"
)
@@ -53,32 +54,18 @@ const (
_SHARED_SIZE = 510
)
type (
vfsLockState uint32
xErrorCode = ExtendedErrorCode
)
type vfsLocker interface {
LockState() vfsLockState
LockShared() xErrorCode // UNLOCKED -> SHARED
LockReserved() xErrorCode // SHARED -> RESERVED
LockPending() xErrorCode // SHARED|RESERVED -> PENDING
LockExclusive() xErrorCode // PENDING -> EXCLUSIVE
DowngradeLock() xErrorCode // SHARED <- EXCLUSIVE|PENDING|RESERVED
Unlock() xErrorCode // UNLOCKED <- EXCLUSIVE|PENDING|RESERVED|SHARED
CheckReservedLock() (bool, xErrorCode)
}
type vfsLockState uint32
type vfsFileLocker struct {
*os.File
state vfsLockState
sync.Mutex
file *os.File
state vfsLockState
shared int
}
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
if assert && (eLock == _NO_LOCK || eLock == _PENDING_LOCK) {
panic(assertErr + " [d4oxww]")
if eLock != _SHARED_LOCK && eLock != _RESERVED_LOCK && eLock != _EXCLUSIVE_LOCK {
panic(assertErr())
}
ptr := vfsFilePtr{mod, pFile}
@@ -89,86 +76,89 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
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]")
}
switch {
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())
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := of.LockState()
fLock := ptr.Locker()
fLock.Lock()
defer fLock.Unlock()
// If some other connection has a lock that precludes the requested lock, return BUSY.
if cLock != fLock && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) {
if cLock != fLock.state && (eLock > _SHARED_LOCK || fLock.state >= _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.
// We are trying for an exclusive lock but another connection is still holding a shared lock.
if eLock == _EXCLUSIVE_LOCK && fLock.shared > 1 {
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]")
if eLock == _SHARED_LOCK && (fLock.state == _SHARED_LOCK || fLock.state == _RESERVED_LOCK) {
if cLock != _NO_LOCK || fLock.shared <= 0 {
panic(assertErr())
}
ptr.SetLock(_SHARED_LOCK)
of.shared++
fLock.shared++
return _OK
}
// Get PENDING lock before acquiring an EXCLUSIVE lock.
if eLock == _EXCLUSIVE_LOCK && cLock == _RESERVED_LOCK {
if rc := fLock.GetPending(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_PENDING_LOCK)
}
// 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 !(fLock.state == _NO_LOCK && fLock.shared == 0) {
panic(assertErr())
}
if rc := of.LockShared(); rc != _OK {
if rc := fLock.GetShared(); rc != _OK {
return uint32(rc)
}
of.shared = 1
ptr.SetLock(_SHARED_LOCK)
fLock.shared = 1
return _OK
case _RESERVED_LOCK:
if rc := of.LockReserved(); rc != _OK {
if !(fLock.state == _SHARED_LOCK && fLock.shared > 0) {
panic(assertErr())
}
if rc := fLock.GetReserved(); 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 !(fLock.state != _NO_LOCK && fLock.shared > 0) {
panic(assertErr())
}
if rc := of.LockExclusive(); rc != _OK {
if rc := fLock.GetExclusive(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_EXCLUSIVE_LOCK)
return _OK
default:
panic(assertErr + " [56ng2l]")
panic(assertErr())
}
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 {
if assert && (eLock != _NO_LOCK && eLock != _SHARED_LOCK) {
panic(assertErr + " [7i4jw3]")
if eLock != _NO_LOCK && eLock != _SHARED_LOCK {
panic(assertErr())
}
ptr := vfsFilePtr{mod, pFile}
@@ -179,20 +169,19 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
return _OK
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := of.LockState()
fLock := ptr.Locker()
fLock.Lock()
defer fLock.Unlock()
if assert && of.shared <= 0 {
panic(assertErr + " [2bhkwg]")
if fLock.shared <= 0 {
panic(assertErr())
}
if cLock > _SHARED_LOCK {
if assert && cLock != fLock {
panic(assertErr + " [6pmjqf]")
if cLock != fLock.state {
panic(assertErr())
}
if eLock == _SHARED_LOCK {
if rc := of.DowngradeLock(); rc != _OK {
if rc := fLock.Downgrade(); rc != _OK {
return uint32(rc)
}
ptr.SetLock(_SHARED_LOCK)
@@ -200,27 +189,25 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
}
}
if assert && eLock != _NO_LOCK {
panic(assertErr + " [gilo9p]")
if eLock != _NO_LOCK {
panic(assertErr())
}
// Decrement the shared lock counter. Release the file lock
// only when all connections have released the lock.
// Release the file lock only when all connections have released the lock.
// Decrement the shared lock counter.
switch {
case of.shared > 1:
ptr.SetLock(_NO_LOCK)
of.shared--
return _OK
case of.shared == 1:
if rc := of.Unlock(); rc != _OK {
case fLock.shared == 1:
if rc := fLock.Release(); rc != _OK {
return uint32(rc)
}
fallthrough
case fLock.shared > 1:
ptr.SetLock(_NO_LOCK)
of.shared = 0
fLock.shared--
return _OK
default:
panic(assertErr + " [50gw51]")
panic(assertErr())
}
}
@@ -228,15 +215,15 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
ptr := vfsFilePtr{mod, pFile}
cLock := ptr.Lock()
if assert && cLock > _SHARED_LOCK {
panic(assertErr + " [zarygt]")
if cLock > _SHARED_LOCK {
panic(assertErr())
}
vfsOpenFilesMtx.Lock()
defer vfsOpenFilesMtx.Unlock()
of := vfsOpenFiles[ptr.ID()]
fLock := ptr.Locker()
fLock.Lock()
defer fLock.Unlock()
locked, rc := of.CheckReservedLock()
locked, rc := fLock.CheckReserved()
if rc != _OK {
return uint32(IOERR_CHECKRESERVEDLOCK)
}

View File

@@ -31,13 +31,13 @@ func Test_vfsLock(t *testing.T) {
defer file2.Close()
vfsOpenFiles = append(vfsOpenFiles, &vfsOpenFile{
file: file1,
nref: 1,
vfsLocker: &vfsFileLocker{file1, _NO_LOCK},
file: file1,
nref: 1,
locker: vfsFileLocker{file: file1},
}, &vfsOpenFile{
file: file2,
nref: 1,
vfsLocker: &vfsFileLocker{file2, _NO_LOCK},
file: file2,
nref: 1,
locker: vfsFileLocker{file: file2},
})
mem := newMemory(128)
@@ -65,7 +65,7 @@ func Test_vfsLock(t *testing.T) {
t.Error("file was locked")
}
rc = vfsLock(context.TODO(), mem.mod, 16, _EXCLUSIVE_LOCK)
rc = vfsLock(context.TODO(), mem.mod, 16, _RESERVED_LOCK)
if rc != _OK {
t.Fatal("returned", rc)
}

View File

@@ -12,13 +12,9 @@ func deleteOnClose(f *os.File) {
_ = os.Remove(f.Name())
}
func (l *vfsFileLocker) LockState() vfsLockState {
return l.state
}
func (l *vfsFileLocker) LockShared() xErrorCode {
if assert && !(l.state == _NO_LOCK) {
panic(assertErr + " [wz9dcw]")
func (l *vfsFileLocker) GetShared() ExtendedErrorCode {
if l.state != _NO_LOCK {
panic(assertErr())
}
// A PENDING lock is needed before acquiring a SHARED lock.
@@ -52,9 +48,9 @@ func (l *vfsFileLocker) LockShared() xErrorCode {
return _OK
}
func (l *vfsFileLocker) LockReserved() xErrorCode {
if assert && !(l.state == _SHARED_LOCK) {
panic(assertErr + " [m9hcil]")
func (l *vfsFileLocker) GetReserved() ExtendedErrorCode {
if l.state != _SHARED_LOCK {
panic(assertErr())
}
// Acquire the RESERVED lock.
@@ -69,9 +65,9 @@ func (l *vfsFileLocker) LockReserved() xErrorCode {
return _OK
}
func (l *vfsFileLocker) LockPending() xErrorCode {
if assert && !(l.state == _SHARED_LOCK || l.state == _RESERVED_LOCK) {
panic(assertErr + " [wx8nk2]")
func (l *vfsFileLocker) GetPending() ExtendedErrorCode {
if l.state != _RESERVED_LOCK {
panic(assertErr())
}
// Acquire the PENDING lock.
@@ -86,9 +82,9 @@ func (l *vfsFileLocker) LockPending() xErrorCode {
return _OK
}
func (l *vfsFileLocker) LockExclusive() xErrorCode {
if assert && !(l.state == _PENDING_LOCK) {
panic(assertErr + " [84nbax]")
func (l *vfsFileLocker) GetExclusive() ExtendedErrorCode {
if l.state != _SHARED_LOCK && l.state != _PENDING_LOCK {
panic(assertErr())
}
// Acquire the EXCLUSIVE lock.
@@ -103,9 +99,9 @@ func (l *vfsFileLocker) LockExclusive() xErrorCode {
return _OK
}
func (l *vfsFileLocker) DowngradeLock() xErrorCode {
if assert && !(l.state > _SHARED_LOCK) {
panic(assertErr + " [je31i3]")
func (l *vfsFileLocker) Downgrade() ExtendedErrorCode {
if l.state <= _SHARED_LOCK {
panic(assertErr())
}
// Downgrade to a SHARED lock.
@@ -134,7 +130,11 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode {
return _OK
}
func (l *vfsFileLocker) Unlock() xErrorCode {
func (l *vfsFileLocker) Release() ExtendedErrorCode {
if l.state <= _NO_LOCK {
panic(assertErr())
}
// Release all locks.
if err := l.fcntlSetLock(&syscall.Flock_t{
Type: syscall.F_UNLCK,
@@ -145,13 +145,15 @@ func (l *vfsFileLocker) Unlock() xErrorCode {
return _OK
}
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
func (l *vfsFileLocker) CheckReserved() (bool, ExtendedErrorCode) {
if l.state >= _RESERVED_LOCK {
return true, _OK
}
// Test all write locks.
// Test the RESERVED lock.
lock := syscall.Flock_t{
Type: syscall.F_RDLCK,
Type: syscall.F_RDLCK,
Start: _RESERVED_BYTE,
Len: 1,
}
if l.fcntlGetLock(&lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
@@ -169,7 +171,7 @@ func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error {
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
F_GETLK = 92 // F_OFD_GETLK
}
return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)
return syscall.FcntlFlock(l.file.Fd(), F_GETLK, lock)
}
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
@@ -182,10 +184,10 @@ func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
F_SETLK = 90 // F_OFD_SETLK
}
return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)
return syscall.FcntlFlock(l.file.Fd(), F_SETLK, lock)
}
func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
func (*vfsFileLocker) errorCode(err error, def ExtendedErrorCode) ExtendedErrorCode {
if errno, ok := err.(syscall.Errno); ok {
switch errno {
case syscall.EACCES:
@@ -195,9 +197,9 @@ func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
case syscall.ENOLCK:
case syscall.EDEADLK:
case syscall.ETIMEDOUT:
return xErrorCode(BUSY)
return ExtendedErrorCode(BUSY)
case syscall.EPERM:
return xErrorCode(PERM)
return ExtendedErrorCode(PERM)
}
}
return def

View File

@@ -4,41 +4,37 @@ import "os"
func deleteOnClose(f *os.File) {}
func (l *vfsFileLocker) LockState() vfsLockState {
return l.state
}
func (l *vfsFileLocker) LockShared() xErrorCode {
func (l *vfsFileLocker) GetShared() ExtendedErrorCode {
l.state = _SHARED_LOCK
return _OK
}
func (l *vfsFileLocker) LockReserved() xErrorCode {
func (l *vfsFileLocker) GetReserved() ExtendedErrorCode {
l.state = _RESERVED_LOCK
return _OK
}
func (l *vfsFileLocker) LockPending() xErrorCode {
func (l *vfsFileLocker) GetPending() ExtendedErrorCode {
l.state = _PENDING_LOCK
return _OK
}
func (l *vfsFileLocker) LockExclusive() xErrorCode {
func (l *vfsFileLocker) GetExclusive() ExtendedErrorCode {
l.state = _EXCLUSIVE_LOCK
return _OK
}
func (l *vfsFileLocker) DowngradeLock() xErrorCode {
func (l *vfsFileLocker) Downgrade() ExtendedErrorCode {
l.state = _SHARED_LOCK
return _OK
}
func (l *vfsFileLocker) Unlock() xErrorCode {
func (l *vfsFileLocker) Release() ExtendedErrorCode {
l.state = _NO_LOCK
return _OK
}
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
func (l *vfsFileLocker) CheckReserved() (bool, ExtendedErrorCode) {
if l.state >= _RESERVED_LOCK {
return true, _OK
}