Skip to content

Commit 6101deb

Browse files
committed
Locking improvements.
1 parent 06eaf41 commit 6101deb

File tree

8 files changed

+153
-160
lines changed

8 files changed

+153
-160
lines changed

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ toolchain go1.23.0
66

77
require (
88
github.com/dchest/siphash v1.2.3
9+
github.com/google/uuid v1.6.0
910
github.com/ncruces/julianday v1.0.0
1011
github.com/ncruces/sort v0.1.2
1112
github.com/psanford/httpreadat v0.1.0
@@ -17,6 +18,4 @@ require (
1718
lukechampine.com/adiantum v1.1.1
1819
)
1920

20-
require github.com/google/uuid v1.6.0
21-
2221
retract v0.4.0 // tagged from the wrong branch

vfs/lock.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,7 @@ func (f *vfsFile) Lock(lock LockLevel) error {
7575
if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
7676
panic(util.AssertErr())
7777
}
78-
reserved := f.lock == LOCK_RESERVED
79-
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
80-
if f.lock < LOCK_PENDING {
81-
// If we're already RESERVED, we can block indefinitely,
82-
// since only incoming readers may briefly hold the PENDING lock.
83-
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
84-
return rc
85-
}
86-
f.lock = LOCK_PENDING
87-
}
88-
// We are now PENDING, so we're just waiting for readers to leave.
89-
// If we were RESERVED, we can block for a bit before invoking the busy handler.
90-
if rc := osGetExclusiveLock(f.File, reserved /* block */); rc != _OK {
78+
if rc := osGetExclusiveLock(f.File, &f.lock); rc != _OK {
9179
return rc
9280
}
9381
f.lock = LOCK_EXCLUSIVE

vfs/os_bsd.go

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,15 @@ package vfs
44

55
import (
66
"os"
7-
"time"
87

98
"golang.org/x/sys/unix"
109
)
1110

12-
func osUnlock(file *os.File, start, len int64) _ErrorCode {
13-
if start == 0 && len == 0 {
14-
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
15-
if err != nil {
16-
return _IOERR_UNLOCK
17-
}
18-
}
19-
return _OK
20-
}
21-
22-
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
23-
err := unix.Flock(int(file.Fd()), how)
24-
return osLockErrorCode(err, def)
25-
}
26-
27-
func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
11+
func osGetSharedLock(file *os.File) _ErrorCode {
2812
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
2913
}
3014

31-
func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
15+
func osGetReservedLock(file *os.File) _ErrorCode {
3216
rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
3317
if rc == _BUSY {
3418
// The documentation states the lock is upgraded by releasing the previous lock,
@@ -38,3 +22,40 @@ func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time
3822
}
3923
return rc
4024
}
25+
26+
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
27+
if *state >= LOCK_RESERVED {
28+
return _OK
29+
}
30+
return osGetReservedLock(file)
31+
}
32+
33+
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
34+
rc := osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
35+
if rc == _BUSY {
36+
// The documentation states the lock is upgraded by releasing the previous lock,
37+
// then acquiring the new lock.
38+
// This is a race, so return IOERR_RDLOCK to ensure the transaction is aborted.
39+
return _IOERR_RDLOCK
40+
}
41+
return _OK
42+
}
43+
44+
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
45+
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
46+
if err != nil {
47+
return _IOERR_UNLOCK
48+
}
49+
return _OK
50+
}
51+
52+
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
53+
// Test the RESERVED lock.
54+
lock, rc := osTestLock(file, _RESERVED_BYTE, 1)
55+
return lock == unix.F_WRLCK, rc
56+
}
57+
58+
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
59+
err := unix.Flock(int(file.Fd()), how)
60+
return osLockErrorCode(err, def)
61+
}

vfs/os_ofd.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//go:build (linux || darwin) && !(sqlite3_flock || sqlite3_nosys)
2+
3+
package vfs
4+
5+
import (
6+
"os"
7+
"time"
8+
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
func osGetSharedLock(file *os.File) _ErrorCode {
13+
// Test the PENDING lock before acquiring a new SHARED lock.
14+
if lock, _ := osTestLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
15+
return _BUSY
16+
}
17+
// Acquire the SHARED lock.
18+
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
19+
}
20+
21+
func osGetReservedLock(file *os.File) _ErrorCode {
22+
// Acquire the RESERVED lock.
23+
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
24+
}
25+
26+
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
27+
if *state == LOCK_RESERVED {
28+
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
29+
if rc := osWriteLock(file, _PENDING_BYTE, 1, -1); rc != _OK {
30+
return rc
31+
}
32+
*state = LOCK_PENDING
33+
}
34+
// Acquire the EXCLUSIVE lock.
35+
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
36+
}
37+
38+
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
39+
if state >= LOCK_EXCLUSIVE {
40+
// Downgrade to a SHARED lock.
41+
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
42+
// notest // this should never happen
43+
return _IOERR_RDLOCK
44+
}
45+
}
46+
// Release the PENDING and RESERVED locks.
47+
return osUnlock(file, _PENDING_BYTE, 2)
48+
}
49+
50+
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
51+
// Release all locks.
52+
return osUnlock(file, 0, 0)
53+
}
54+
55+
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
56+
// Test the RESERVED lock.
57+
lock, rc := osTestLock(file, _RESERVED_BYTE, 1)
58+
return lock == unix.F_WRLCK, rc
59+
}

vfs/os_unix.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,41 @@ func osSetMode(file *os.File, modeof string) error {
3131
}
3232
return nil
3333
}
34+
35+
func osTestLock(file *os.File, start, len int64) (int16, _ErrorCode) {
36+
lock := unix.Flock_t{
37+
Type: unix.F_WRLCK,
38+
Start: start,
39+
Len: len,
40+
}
41+
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
42+
return 0, _IOERR_CHECKRESERVEDLOCK
43+
}
44+
return lock.Type, _OK
45+
}
46+
47+
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
48+
if err == nil {
49+
return _OK
50+
}
51+
if errno, ok := err.(unix.Errno); ok {
52+
switch errno {
53+
case
54+
unix.EACCES,
55+
unix.EAGAIN,
56+
unix.EBUSY,
57+
unix.EINTR,
58+
unix.ENOLCK,
59+
unix.EDEADLK,
60+
unix.ETIMEDOUT:
61+
return _BUSY
62+
case unix.EPERM:
63+
return _PERM
64+
}
65+
// notest // usually EWOULDBLOCK == EAGAIN
66+
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
67+
return _BUSY
68+
}
69+
}
70+
return def
71+
}

vfs/os_unix_lock.go

Lines changed: 0 additions & 108 deletions
This file was deleted.

vfs/os_windows.go

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,25 @@ func osGetReservedLock(file *os.File) _ErrorCode {
2828
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
2929
}
3030

31-
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
32-
var timeout time.Duration
33-
if block {
34-
timeout = -1
35-
}
36-
37-
// Acquire the PENDING lock.
38-
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
39-
}
40-
41-
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode {
42-
var timeout time.Duration
43-
if block {
44-
timeout = time.Millisecond
31+
func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
32+
// A PENDING lock is needed before releasing the SHARED lock.
33+
if *state < LOCK_PENDING {
34+
// If we were RESERVED, we can block indefinitely.
35+
var timeout time.Duration
36+
if *state == LOCK_RESERVED {
37+
timeout = -1
38+
}
39+
if rc := osWriteLock(file, _PENDING_BYTE, 1, timeout); rc != _OK {
40+
return rc
41+
}
42+
*state = LOCK_PENDING
4543
}
4644

4745
// Release the SHARED lock.
4846
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
4947

5048
// Acquire the EXCLUSIVE lock.
51-
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
49+
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
5250

5351
if rc != _OK {
5452
// Reacquire the SHARED lock.
@@ -64,9 +62,7 @@ func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
6462

6563
// Reacquire the SHARED lock.
6664
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
67-
// This should never happen.
68-
// We should always be able to reacquire the read lock.
69-
// notest
65+
// notest // this should never happen
7066
return _IOERR_RDLOCK
7167
}
7268
}

vfs/shm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (s *vfsShm) shmOpen() _ErrorCode {
7070
}
7171

7272
// Dead man's switch.
73-
if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK {
73+
if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK {
7474
return _IOERR_LOCK
7575
} else if lock == unix.F_WRLCK {
7676
return _BUSY

0 commit comments

Comments
 (0)