-
Notifications
You must be signed in to change notification settings - Fork 926
/
keylock.go
75 lines (62 loc) · 1.66 KB
/
keylock.go
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
package keylock
import (
"sync"
"time"
)
type bucket struct {
expires time.Time
handle int64
}
// KeyLock is a simple implementation of key based locks with ttl's on them
type KeyLock struct {
locks map[interface{}]*bucket
locksMU sync.Mutex
c int64
}
func NewKeyLock() *KeyLock {
return &KeyLock{
locks: make(map[interface{}]*bucket),
}
}
// Lock attempts to lock the specified key for the specified duration, expiring after ttl
// if it fails to grab the key after timeout it will return -1,
// it will return a lock handle otherwise that you use to unlock it with.
// this is to protect against you unlocking it after the ttl expired when something else is holding it
func (kl *KeyLock) Lock(key interface{}, timeout time.Duration, ttl time.Duration) int64 {
started := time.Now()
for {
if handle := kl.tryLock(key, ttl); handle != -1 {
return handle
}
if time.Since(started) >= timeout {
return -1
}
time.Sleep(time.Millisecond * 250)
}
}
func (kl *KeyLock) tryLock(key interface{}, ttl time.Duration) int64 {
kl.locksMU.Lock()
now := time.Now()
// if there is no lock, or were past the expiry of it
if b, ok := kl.locks[key]; !ok || (b == nil || now.After(b.expires)) {
// then we can sucessfully lock it
kl.c++
handle := kl.c
kl.locks[key] = &bucket{
handle: handle,
expires: now.Add(ttl),
}
kl.locksMU.Unlock()
return handle
}
kl.locksMU.Unlock()
return -1
}
func (kl *KeyLock) Unlock(key interface{}, handle int64) {
kl.locksMU.Lock()
if b, ok := kl.locks[key]; ok && b != nil && b.handle == handle {
// only delete it if the caller is the one holding the lock
delete(kl.locks, key)
}
kl.locksMU.Unlock()
}