Skip to content

Commit d486ff2

Browse files
authored
Wal flock (#177)
* feat: use flock for WAL * fix * save * save
1 parent b0efa97 commit d486ff2

8 files changed

Lines changed: 174 additions & 133 deletions

File tree

diskcache/diskcache.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ type DiskCache struct {
9090
rlock *InstrumentedMutex // read-lock: used to exclude concurrent Get on the tail file.
9191
rwlock *InstrumentedMutex // used to exclude switch/rotate/drop/Close on current disk cache instance.
9292

93-
flock *flock // disabled multi-Open on same path
94-
pos *pos // current read fd position info
93+
flock *walLock // disabled multi-Open on same path
94+
pos *pos // current read fd position info
9595

9696
// specs of current diskcache
9797
size atomic.Int64 // current byte size

diskcache/flock.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the MIT License.
3+
// This product includes software developed at Guance Cloud (https://www.guance.com/).
4+
// Copyright 2021-present Guance, Inc.
5+
6+
package diskcache
7+
8+
import (
9+
"os"
10+
"path/filepath"
11+
)
12+
13+
type walLock struct {
14+
file string
15+
f *os.File
16+
}
17+
18+
func newFlock(path string) *walLock {
19+
file := filepath.Clean(filepath.Join(path, ".lock"))
20+
21+
return &walLock{
22+
file: file,
23+
}
24+
}
Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,33 @@ package diskcache
77

88
import (
99
"os"
10-
"runtime"
10+
"path/filepath"
1111
"sync"
1212
T "testing"
1313
"time"
1414

1515
"github.com/stretchr/testify/assert"
1616
)
1717

18-
func TestPidAlive(t *T.T) {
19-
t.Run("pid-1", func(t *T.T) {
20-
if runtime.GOOS != "windows" {
21-
assert.True(t, pidAlive(1))
22-
}
23-
})
18+
func TestLockUnlock(t *T.T) {
19+
t.Run("unlock-remove", func(t *T.T) {
20+
p := t.TempDir()
21+
fl := newFlock(p)
2422

25-
t.Run("pid-not-exist", func(t *T.T) {
26-
assert.False(t, pidAlive(-1))
27-
})
23+
ok, err := fl.tryLock()
24+
assert.True(t, ok)
25+
assert.NoError(t, err)
26+
27+
fi, err := os.Stat(filepath.Join(p, ".lock"))
28+
assert.NoError(t, err)
29+
t.Logf("fi: %+#v", fi)
2830

29-
t.Run("cur-pid", func(t *T.T) {
30-
assert.True(t, pidAlive(os.Getpid()))
31+
fl.unlock()
32+
33+
_, err = os.Stat(filepath.Join(p, ".lock"))
34+
assert.Error(t, err)
3135
})
32-
}
3336

34-
func TestLockUnlock(t *T.T) {
3537
t.Run("lock", func(t *T.T) {
3638
p := t.TempDir()
3739

@@ -42,7 +44,10 @@ func TestLockUnlock(t *T.T) {
4244
defer wg.Done()
4345
fl := newFlock(p)
4446

45-
assert.NoError(t, fl.lock())
47+
ok, err := fl.tryLock()
48+
49+
assert.True(t, ok)
50+
assert.NoError(t, err)
4651
defer fl.unlock()
4752

4853
time.Sleep(time.Second * 5)
@@ -54,7 +59,8 @@ func TestLockUnlock(t *T.T) {
5459
defer wg.Done()
5560
fl := newFlock(p)
5661

57-
err := fl.lock()
62+
ok, err := fl.tryLock()
63+
assert.False(t, ok)
5864
assert.Error(t, err)
5965

6066
t.Logf("[expect] err: %s", err.Error())
@@ -68,7 +74,7 @@ func TestLockUnlock(t *T.T) {
6874

6975
// try lock until ok
7076
for {
71-
if err := fl.lock(); err != nil {
77+
if ok, err := fl.tryLock(); !ok {
7278
t.Logf("[expect] err: %s", err.Error())
7379
time.Sleep(time.Second)
7480
} else {

diskcache/flock_unix.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the MIT License.
3+
// This product includes software developed at Guance Cloud (https://www.guance.com/).
4+
// Copyright 2021-present Guance, Inc.
5+
6+
//go:build !windows
7+
8+
package diskcache
9+
10+
import (
11+
"errors"
12+
"fmt"
13+
"os"
14+
"syscall"
15+
)
16+
17+
func (wl *walLock) tryLock() (bool, error) {
18+
f, err := os.OpenFile(wl.file, os.O_CREATE|os.O_RDWR, 0o666) // nolint: gosec
19+
if err != nil {
20+
return false, err
21+
}
22+
wl.f = f
23+
24+
// LOCK_EX = Exclusive, LOCK_NB = Non-blocking
25+
err = syscall.Flock(int(wl.f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
26+
if err != nil {
27+
// If the error is EWOULDBLOCK, it means someone else has the lock
28+
if errors.Is(err, syscall.EWOULDBLOCK) {
29+
if err := wl.f.Close(); err != nil {
30+
l.Errorf("Close: %s", err.Error())
31+
}
32+
return false, fmt.Errorf("locked")
33+
}
34+
35+
if err := wl.f.Close(); err != nil {
36+
l.Errorf("Close: %s", err.Error())
37+
}
38+
return false, err
39+
}
40+
return true, nil
41+
}
42+
43+
func (wl *walLock) unlock() {
44+
if wl.f != nil {
45+
if err := syscall.Flock(int(wl.f.Fd()), syscall.LOCK_UN); err != nil {
46+
l.Errorf("Flock: %s", err.Error())
47+
}
48+
49+
if err := wl.f.Close(); err != nil {
50+
l.Errorf("CLose: %s", err.Error())
51+
}
52+
53+
if err := os.Remove(wl.file); err != nil { // Optional on Unix
54+
l.Errorf("Remove: %s", err.Error())
55+
}
56+
}
57+
}

diskcache/flock_windows.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the MIT License.
3+
// This product includes software developed at Guance Cloud (https://www.guance.com/).
4+
// Copyright 2021-present Guance, Inc.
5+
6+
//go:build windows
7+
// +build windows
8+
9+
package diskcache
10+
11+
import (
12+
"os"
13+
"syscall"
14+
"unsafe"
15+
)
16+
17+
var (
18+
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
19+
procLockFileEx = modkernel32.NewProc("LockFileEx")
20+
)
21+
22+
func (wl *walLock) tryLock() (bool, error) {
23+
f, err := os.OpenFile(wl.file, os.O_CREATE|os.O_RDWR, 0o666)
24+
if err != nil {
25+
return false, err
26+
}
27+
wl.f = f
28+
29+
// LOCKFILE_EXCLUSIVE_LOCK = 2, LOCKFILE_FAIL_IMMEDIATELY = 1
30+
flags := uint32(2 | 1)
31+
var overlapped syscall.Overlapped
32+
33+
// Call Win32 LockFileEx
34+
ret, _, err := procLockFileEx.Call(
35+
uintptr(wl.f.Fd()),
36+
uintptr(flags),
37+
0, // reserved
38+
0, // length low
39+
1, // length high (lock 1 byte)
40+
uintptr(unsafe.Pointer(&overlapped)),
41+
)
42+
43+
if ret == 0 {
44+
// ERROR_LOCK_VIOLATION = 33
45+
if errno, ok := err.(syscall.Errno); ok && errno == 33 {
46+
wl.f.Close()
47+
return false, nil
48+
}
49+
wl.f.Close()
50+
return false, err
51+
}
52+
53+
return true, nil
54+
}
55+
56+
func (wl *walLock) unlock() {
57+
if wl.f != nil {
58+
wl.f.Close() // Closing the file handle automatically releases the lock in Windows
59+
os.Remove(wl.file)
60+
}
61+
}

diskcache/lock.go

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

diskcache/open.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ func (c *DiskCache) doOpen() error {
107107
// disable open multiple times
108108
if !c.noLock {
109109
fl := newFlock(c.path)
110-
if err := fl.lock(); err != nil {
110+
if ok, err := fl.tryLock(); !ok {
111111
return WrapLockError(err, c.path, 0).WithDetails("failed_to_acquire_directory_lock")
112112
} else {
113+
l.Infof("locked file %s ok", fl.file)
113114
c.flock = fl
114115
}
115116
}
@@ -205,9 +206,7 @@ func (c *DiskCache) Close() error {
205206

206207
if !c.noLock {
207208
if c.flock != nil {
208-
if err := c.flock.unlock(); err != nil {
209-
return WrapLockError(err, c.path, 0).WithDetails("failed_to_release_directory_lock")
210-
}
209+
c.flock.unlock()
211210
}
212211
}
213212

diskcache/open_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ func TestOpen(t *T.T) {
2222
// lock then no-lock
2323
c, err := Open(WithPath(p))
2424
assert.NoError(t, err)
25+
26+
assert.FileExists(t, filepath.Join(p, ".lock"))
27+
2528
assert.NoError(t, c.Close())
29+
assert.NoFileExists(t, filepath.Join(p, ".lock"))
2630

2731
c2, err := Open(WithPath(p), WithNoPos(true))
2832
assert.NoError(t, err)

0 commit comments

Comments
 (0)