-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlock.go
More file actions
118 lines (108 loc) · 2.52 KB
/
Copy pathlock.go
File metadata and controls
118 lines (108 loc) · 2.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package durable
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/gofrs/flock"
)
// dirOccupancy tracks in-process Engine / ReadOnlyEngine opens for a dataDir.
// OS flock is associated with a file description and can succeed twice in the
// same process on some platforms; this map is what makes a second NewEngine
// on the same dataDir fail inside one process. Flock still serialises
// distinct OS processes.
type dirOccupancy struct {
writers int
readers int
}
var (
occupancyMu sync.Mutex
occupancy = map[string]*dirOccupancy{}
)
func occupyExclusive(absDir string) error {
occupancyMu.Lock()
defer occupancyMu.Unlock()
o := occupancy[absDir]
if o == nil {
occupancy[absDir] = &dirOccupancy{writers: 1}
return nil
}
if o.writers > 0 || o.readers > 0 {
return ErrEngineLocked
}
o.writers = 1
return nil
}
func occupyShared(absDir string) error {
occupancyMu.Lock()
defer occupancyMu.Unlock()
o := occupancy[absDir]
if o == nil {
occupancy[absDir] = &dirOccupancy{readers: 1}
return nil
}
if o.writers > 0 {
return ErrEngineLocked
}
o.readers++
return nil
}
func releaseOccupancy(absDir string, exclusive bool) {
occupancyMu.Lock()
defer occupancyMu.Unlock()
o := occupancy[absDir]
if o == nil {
return
}
if exclusive {
o.writers--
} else {
o.readers--
}
if o.writers <= 0 && o.readers <= 0 {
delete(occupancy, absDir)
}
}
// acquireLock takes an exclusive or shared flock on path, waiting up to
// timeout (or ctx, whichever ends first). Exclusive is used by NewEngine so
// a writer never shares the journal with another writer or a reader.
// Shared is used by NewReadOnlyEngine so multiple CLI readers can coexist.
func acquireLock(ctx context.Context, path string, exclusive bool, timeout time.Duration) (*flock.Flock, error) {
if ctx == nil {
ctx = context.Background()
}
lockCtx := ctx
var cancel context.CancelFunc
if timeout > 0 {
lockCtx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}
const retryDelay = 50 * time.Millisecond
f := flock.New(path)
var (
ok bool
err error
)
if exclusive {
ok, err = f.TryLockContext(lockCtx, retryDelay)
} else {
ok, err = f.TryRLockContext(lockCtx, retryDelay)
}
if err != nil {
if errors.Is(ctx.Err(), context.Canceled) {
return nil, fmt.Errorf("durable: lock cancelled: %w", ctx.Err())
}
return nil, fmt.Errorf("%w: %v", ErrEngineLocked, err)
}
if !ok {
return nil, ErrEngineLocked
}
return f, nil
}
func releaseLock(f *flock.Flock) error {
if f == nil {
return nil
}
return f.Unlock()
}