Skip to content

Commit 71d95bf

Browse files
committed
Fix #205.
1 parent 7e23100 commit 71d95bf

File tree

5 files changed

+103
-14
lines changed

5 files changed

+103
-14
lines changed

internal/dotlk/dotlk.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package dotlk
2+
3+
import (
4+
"errors"
5+
"io/fs"
6+
"os"
7+
)
8+
9+
// LockShm creates a directory on disk to prevent SQLite
10+
// from using this path for a shared memory file.
11+
func LockShm(name string) error {
12+
err := os.Mkdir(name, 0777)
13+
if errors.Is(err, fs.ErrExist) {
14+
s, err := os.Lstat(name)
15+
if err == nil && s.IsDir() {
16+
return nil
17+
}
18+
}
19+
return err
20+
}
21+
22+
// Unlock removes the lock or shared memory file.
23+
func Unlock(name string) error {
24+
err := os.Remove(name)
25+
if errors.Is(err, fs.ErrNotExist) {
26+
return nil
27+
}
28+
return err
29+
}

internal/dotlk/dotlk_other.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !unix
2+
3+
package dotlk
4+
5+
import "os"
6+
7+
// TryLock returns nil if it acquired the lock,
8+
// fs.ErrExist if another process has the lock.
9+
func TryLock(name string) error {
10+
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
11+
f.Close()
12+
return err
13+
}

internal/dotlk/dotlk_unix.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//go:build unix
2+
3+
package dotlk
4+
5+
import (
6+
"errors"
7+
"io/fs"
8+
"os"
9+
"strconv"
10+
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
// TryLock returns nil if it acquired the lock,
15+
// fs.ErrExist if another process has the lock.
16+
func TryLock(name string) error {
17+
for retry := true; retry; retry = false {
18+
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
19+
if err == nil {
20+
f.WriteString(strconv.Itoa(os.Getpid()))
21+
f.Close()
22+
return nil
23+
}
24+
if !errors.Is(err, fs.ErrExist) {
25+
return err
26+
}
27+
if !removeStale(name) {
28+
break
29+
}
30+
}
31+
return fs.ErrExist
32+
}
33+
34+
func removeStale(name string) bool {
35+
buf, err := os.ReadFile(name)
36+
if err != nil {
37+
return errors.Is(err, fs.ErrNotExist)
38+
}
39+
40+
pid, err := strconv.Atoi(string(buf))
41+
if err != nil {
42+
return false
43+
}
44+
if unix.Kill(pid, 0) == nil {
45+
return false
46+
}
47+
48+
err = os.Remove(name)
49+
return err == nil || errors.Is(err, fs.ErrNotExist)
50+
}

vfs/os_dotlk.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io/fs"
88
"os"
99
"sync"
10+
11+
"github.com/ncruces/go-sqlite3/internal/dotlk"
1012
)
1113

1214
var (
@@ -28,12 +30,10 @@ func osGetSharedLock(file *os.File) _ErrorCode {
2830
name := file.Name()
2931
locker := vfsDotLocks[name]
3032
if locker == nil {
31-
f, err := os.OpenFile(name+".lock", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
32-
f.Close()
33-
if errors.Is(err, fs.ErrExist) {
34-
return _BUSY // Another process has the lock.
35-
}
36-
if err != nil {
33+
if err := dotlk.TryLock(name + ".lock"); err != nil {
34+
if errors.Is(err, fs.ErrExist) {
35+
return _BUSY // Another process has the lock.
36+
}
3737
return _IOERR_LOCK
3838
}
3939
locker = &vfsDotLocker{}
@@ -114,8 +114,7 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
114114
}
115115

116116
if locker.shared == 1 {
117-
err := os.Remove(name + ".lock")
118-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
117+
if err := dotlk.Unlock(name + ".lock"); err != nil {
119118
return _IOERR_UNLOCK
120119
}
121120
delete(vfsDotLocks, name)

vfs/shm_dotlk.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import (
66
"context"
77
"errors"
88
"io/fs"
9-
"os"
109
"sync"
1110

1211
"github.com/tetratelabs/wazero/api"
1312

13+
"github.com/ncruces/go-sqlite3/internal/dotlk"
1414
"github.com/ncruces/go-sqlite3/internal/util"
1515
)
1616

@@ -58,8 +58,7 @@ func (s *vfsShm) Close() error {
5858
return nil
5959
}
6060

61-
err := os.Remove(s.path)
62-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
61+
if err := dotlk.Unlock(s.path); err != nil {
6362
return _IOERR_UNLOCK
6463
}
6564
delete(vfsShmList, s.path)
@@ -82,9 +81,8 @@ func (s *vfsShm) shmOpen() _ErrorCode {
8281
return _OK
8382
}
8483

85-
// Create a directory on disk to ensure only this process
86-
// uses this path to register a shared memory.
87-
err := os.Mkdir(s.path, 0777)
84+
// Dead man's switch.
85+
err := dotlk.LockShm(s.path)
8886
if errors.Is(err, fs.ErrExist) {
8987
return _BUSY
9088
}

0 commit comments

Comments
 (0)