-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.go
More file actions
104 lines (93 loc) · 2.3 KB
/
main.go
File metadata and controls
104 lines (93 loc) · 2.3 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
package flock
import (
"context"
"encoding/json"
"errors"
"os"
"time"
"github.com/opensvc/locker"
)
type (
// T wraps flock and dumps JSON data in the lock file
// hinting about what holds the lock.
T struct {
locker.Locker
Path string
sessionId string
}
Meta struct {
At time.Time `json:"at"`
PID int `json:"pid"`
Intent string `json:"intent"`
SessionID string `json:"session_id"`
}
)
var (
truncate = os.Truncate
remove = os.Remove
retryInterval = 500 * time.Millisecond
)
// New allocate a file lock struct from the Locker provider.
func New(p string, sessionId string, lockP func(string) locker.Locker) *T {
return &T{
Locker: lockP(p),
Path: p,
sessionId: sessionId,
}
}
// Lock acquires an exclusive file lock on the file and writes a JSON
// formatted structure hinting who holds the lock and with what
// intention.
func (t *T) Lock(timeout time.Duration, intent string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err = t.LockContext(ctx, retryInterval)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return errors.New("lock timeout exceeded")
}
return
}
err = t.writeMeta(intent)
return
}
func (t *T) writeMeta(intent string) error {
m := Meta{
At: time.Now(),
PID: os.Getpid(),
Intent: intent,
SessionID: t.sessionId,
}
enc := json.NewEncoder(t)
return enc.Encode(m)
}
// Probe attempts to acquire a file lock. If successful, it releases the lock and returns an empty Meta.
// If the lock is already held by another process, it reads and returns the current lock metadata.
// Note: Reading the metadata may fail if the other process has not finished writing it.
func (t *T) Probe() (Meta, error) {
err := t.TryLock()
if err == nil {
// lock acquired
_ = t.UnLock()
return Meta{}, nil
}
// lock conflict, read current lock meta
return t.readMeta()
}
func (t *T) readMeta() (Meta, error) {
var m Meta
file, err := os.Open(t.Path)
if err != nil {
return m, err
}
defer func() { _ = file.Close() }()
dec := json.NewDecoder(file)
err = dec.Decode(&m)
return m, err
}
// UnLock releases the file lock acquired by Lock.
func (t *T) UnLock() error {
_ = truncate(t.Path, 0)
_ = remove(t.Path)
return t.Locker.UnLock()
}