generated from mrz1836/go-template
-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlock.go
141 lines (114 loc) · 3.64 KB
/
lock.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
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package cachestore
import (
"context"
"time"
"github.com/mrz1836/go-cache"
"github.com/pkg/errors"
)
// WriteLock will create a unique lock/secret with a TTL (seconds) to expire
// The lockKey is unique and should be deterministic
// The secret will be automatically generated and stored in the locked key (returned)
func (c *Client) WriteLock(ctx context.Context, lockKey string, ttl int64) (string, error) {
var secret string
var err error
// Create a secret
if secret, err = RandomHex(32); err != nil {
// This will "ALMOST NEVER" error out
return "", errors.Wrap(ErrSecretGenerationFailed, err.Error())
}
// Test the key and secret
if err = validateLockValues(lockKey, secret); err != nil {
return "", err
}
// Lock using Redis
if c.Engine() == Redis {
if _, err = cache.WriteLock(
ctx, c.options.redis, lockKey, secret, ttl,
); err != nil {
return "", errors.Wrap(ErrLockCreateFailed, err.Error())
}
} else if c.Engine() == FreeCache { // Lock using FreeCache
if _, err = writeLockFreeCache(
c.options.freeCache, lockKey, secret, ttl,
); err != nil {
return "", errors.Wrap(ErrLockCreateFailed, err.Error())
}
}
return secret, nil
}
// WriteLockWithSecret will create a lock with the given secret with a TTL (seconds) to expire
// The lockKey is unique and should be deterministic
// The secret should be unique per instance/process that wants to acquire the lock
func (c *Client) WriteLockWithSecret(ctx context.Context, lockKey, secret string, ttl int64) (string, error) {
var err error
// Test the key and secret
if err = validateLockValues(lockKey, secret); err != nil {
return "", err
}
// Lock using Redis
if c.Engine() == Redis {
if _, err = cache.WriteLock(
ctx, c.options.redis, lockKey, secret, ttl,
); err != nil {
return "", errors.Wrap(ErrLockCreateFailed, err.Error())
}
} else if c.Engine() == FreeCache { // Lock using FreeCache
if _, err = writeLockFreeCache(
c.options.freeCache, lockKey, secret, ttl,
); err != nil {
return "", errors.Wrap(ErrLockCreateFailed, err.Error())
}
}
return secret, nil
}
// WaitWriteLock will aggressively try to make a lock until the TTW (in seconds) is reached
func (c *Client) WaitWriteLock(ctx context.Context, lockKey string, ttl, ttw int64) (string, error) {
var secret string
// Test the values
if len(lockKey) == 0 {
return secret, ErrKeyRequired
} else if ttw <= 0 {
return secret, ErrTTWCannotBeEmpty
}
// Create the end time for the loop
end := time.Now().Add(time.Duration(ttw) * time.Second)
// Loop until we have a secret, or we are passed the end time
for {
if secret, _ = c.WriteLock(
ctx, lockKey, ttl,
); len(secret) > 0 || time.Now().After(end) {
break
}
time.Sleep(lockRetrySleepTime)
}
// No secret, lock creating failed or did not complete
if len(secret) == 0 {
return "", ErrLockCreateFailed
}
return secret, nil
}
// ReleaseLock will release a given lock key only if the secret matches
func (c *Client) ReleaseLock(ctx context.Context, lockKey, secret string) (bool, error) {
// Test the key and secret
if err := validateLockValues(lockKey, secret); err != nil {
return false, err
}
// Release the lock
if c.Engine() == Redis {
return cache.ReleaseLock(ctx, c.options.redis, lockKey, secret)
}
// Default is FreeCache
return releaseLockFreeCache(c.options.freeCache, lockKey, secret)
}
// validateLockValues will validate and test the lock/secret values
func validateLockValues(lockKey, secret string) error {
// Require a key to be present
if len(lockKey) == 0 {
return ErrKeyRequired
}
// Require a secret to be present
if len(secret) == 0 {
return ErrSecretRequired
}
return nil
}