redis_getlock 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +6 -4
- data/bin/try +20 -2
- data/lib/redis_getlock.rb +39 -16
- data/lib/redis_getlock/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e211a307e1814231fd3bff20fe5a435c4086bd4b
|
4
|
+
data.tar.gz: 8eff81ba4ddc1bdbd434cc730c676684b3b6a24f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5066ccec8e8b53c5b8f8a58c0d80740921a7627431918ed2a92d3533620c6fa1787c72d3aeaa7b6f0ce6899b434ddd236b14ee1c3c87435e357b1890040bf34
|
7
|
+
data.tar.gz: 77b1d0ba859c9e7b0dd04ee59473c9aefc2e785ca03a131902c342da4c6418eee9646f7b0626b38e2bc821ad8ca6560a8c7f087cae1f10be4655d9f20a327e6f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -28,7 +28,7 @@ However, how long should we set if we are uncertain how long a job takes?
|
|
28
28
|
This gem takes a following approach to resolve this problem.
|
29
29
|
|
30
30
|
1. Expiration time is set to `2` (default) seconds
|
31
|
-
2. Extend the lock in each `1` (default)
|
31
|
+
2. Extend the lock in each `1` (default) second interval invoking another thread
|
32
32
|
|
33
33
|
This way ensures to release orphaned lock in 2 seconds. We are released from caring of the value of `expire`!!
|
34
34
|
|
@@ -75,13 +75,13 @@ Or install it yourself as:
|
|
75
75
|
Similarly with ruby standard library [mutex](https://ruby-doc.org/core-2.2.0/Mutex.html), following methods are available:
|
76
76
|
|
77
77
|
* lock
|
78
|
-
* Attempts to grab the lock and waits if it isn’t available.
|
78
|
+
* Attempts to grab the lock and waits if it isn’t available. Returns true if successfully acquired a lock
|
79
79
|
* locked?
|
80
80
|
* Returns true if this lock is currently held by some (including myself).
|
81
81
|
* synchronize {}
|
82
|
-
* Obtains a lock, runs the block, and releases the lock when the block completes.
|
82
|
+
* Obtains a lock, runs the block, and releases the lock when the block completes. Raises `RedisGetlock::LockError` error when failed to acquire a lock.
|
83
83
|
* unlock
|
84
|
-
* Releases the lock.
|
84
|
+
* Releases the lock. Returns true if successfully released a lock
|
85
85
|
* self_locked?
|
86
86
|
* Returns true if this lock is currently held by myself.
|
87
87
|
|
@@ -91,6 +91,8 @@ Options of `RedisGetlock.new` are:
|
|
91
91
|
* Provide a redis instance
|
92
92
|
* key
|
93
93
|
* Key name for a distributed lock
|
94
|
+
* timeout
|
95
|
+
* The timeout of trying to get the lock. A negative value means infinite timeout (default: -1)
|
94
96
|
* logger
|
95
97
|
* Provide a logger for RedisGetlock (for debug)
|
96
98
|
* expire
|
data/bin/try
CHANGED
@@ -3,14 +3,32 @@
|
|
3
3
|
require "bundler/setup"
|
4
4
|
require "redis_getlock"
|
5
5
|
require 'logger'
|
6
|
+
require 'optparse'
|
6
7
|
|
7
|
-
|
8
|
+
opts = {
|
9
|
+
timeout: -1,
|
10
|
+
kill: false
|
11
|
+
}
|
12
|
+
OptionParser.new.tap {|op|
|
13
|
+
op.on('--timeout VALUE') {|v|
|
14
|
+
opts[:timeout] = Float(v)
|
15
|
+
}
|
16
|
+
op.on('--kill') {|v|
|
17
|
+
opts[:kill] = true
|
18
|
+
}
|
19
|
+
op.parse(ARGV)
|
20
|
+
}
|
21
|
+
|
22
|
+
if opts[:kill]
|
8
23
|
trap('INT') do
|
9
24
|
exit!
|
10
25
|
end
|
11
26
|
end
|
12
27
|
|
13
|
-
mutex = RedisGetlock.new(
|
28
|
+
mutex = RedisGetlock.new(
|
29
|
+
redis: Redis.new, key: 'redis_getlock', logger: Logger.new(STDOUT),
|
30
|
+
timeout: opts[:timeout],
|
31
|
+
)
|
14
32
|
puts 'redis-cli> del redis_getlock'
|
15
33
|
mutex.synchronize do
|
16
34
|
loop do
|
data/lib/redis_getlock.rb
CHANGED
@@ -4,35 +4,55 @@ require 'securerandom'
|
|
4
4
|
require 'json'
|
5
5
|
|
6
6
|
class RedisGetlock
|
7
|
-
attr_reader :redis, :key, :logger, :expire, :interval, :uuid
|
7
|
+
attr_reader :redis, :key, :logger, :timeout, :expire, :interval, :uuid
|
8
8
|
|
9
|
+
TIMEOUT = -1
|
9
10
|
EXPIRE = 2
|
10
11
|
INTERVAL = 1
|
11
12
|
|
12
|
-
|
13
|
+
class LockError < ::StandardError; end
|
14
|
+
|
15
|
+
def initialize(redis:, key:, logger: nil, timeout: TIMEOUT, expire: EXPIRE, interval: INTERVAL)
|
13
16
|
@redis = redis
|
14
17
|
@key = key
|
15
18
|
@logger = logger
|
19
|
+
@timeout = timeout
|
16
20
|
@expire = expire
|
17
21
|
@interval = interval
|
18
22
|
@uuid = SecureRandom.uuid
|
19
23
|
end
|
20
24
|
|
21
25
|
def lock
|
22
|
-
logger.info { "#{log_head}Wait
|
26
|
+
logger.info { "#{log_head}Wait #{timeout < 0 ? '' : "#{timeout} sec "}to acquire a mysql lock '#{key}'" } if logger
|
23
27
|
if set_options_available?
|
24
|
-
lock_with_set_options
|
28
|
+
locked = lock_with_set_options
|
29
|
+
else
|
30
|
+
locked = lock_without_set_options
|
31
|
+
end
|
32
|
+
@thr.terminate if @thr and @thr.alive?
|
33
|
+
if locked
|
34
|
+
@thr = Thread.new(&method(:keeplock))
|
35
|
+
logger.info { "#{log_head}Acquired a redis lock '#{key}'" } if logger
|
36
|
+
true
|
25
37
|
else
|
26
|
-
|
38
|
+
logger.info { "#{log_head}Timeout to acquire a redis lock '#{key}'" } if logger
|
39
|
+
false
|
27
40
|
end
|
28
|
-
@thr = Thread.new(&method(:keeplock))
|
29
|
-
logger.info { "#{log_head}Acquired a redis lock '#{key}'" } if logger
|
30
41
|
end
|
31
42
|
|
32
43
|
def unlock
|
33
|
-
@thr.terminate
|
34
|
-
|
35
|
-
|
44
|
+
@thr.terminate if @thr and @thr.alive?
|
45
|
+
if self_locked?
|
46
|
+
redis.del(key)
|
47
|
+
logger.info { "#{log_head}Released a redis lock '#{key}'" } if logger
|
48
|
+
true
|
49
|
+
elsif locked?
|
50
|
+
logger.info { "#{log_head}Failed to release a redis lock since somebody else locked '#{key}'" } if logger
|
51
|
+
false
|
52
|
+
else
|
53
|
+
logger.info { "#{log_head}Redis lock did not exist '#{key}'" } if logger
|
54
|
+
true
|
55
|
+
end
|
36
56
|
end
|
37
57
|
|
38
58
|
def locked?
|
@@ -40,13 +60,13 @@ class RedisGetlock
|
|
40
60
|
end
|
41
61
|
|
42
62
|
def self_locked?
|
43
|
-
|
63
|
+
locked? && uuid == JSON.parse(redis.get(key))['uuid']
|
44
64
|
end
|
45
65
|
|
46
66
|
def synchronize(&block)
|
47
|
-
lock
|
67
|
+
raise LockError unless lock
|
48
68
|
begin
|
49
|
-
yield
|
69
|
+
return yield
|
50
70
|
ensure
|
51
71
|
unlock
|
52
72
|
end
|
@@ -67,10 +87,12 @@ class RedisGetlock
|
|
67
87
|
# redis >= 2.6.12
|
68
88
|
# ref. http://redis.io/commands/set
|
69
89
|
def lock_with_set_options
|
90
|
+
started = Time.now.to_f
|
70
91
|
loop do
|
71
92
|
current = Time.now.to_f
|
72
93
|
payload = {uuid: uuid, expire_at: (current + expire).to_s}.to_json
|
73
|
-
|
94
|
+
return true if redis.set(key, payload, {nx: true, ex: expire}) # key does not exist
|
95
|
+
return false if timeout >= 0 and (current - started) >= timeout
|
74
96
|
sleep interval
|
75
97
|
end
|
76
98
|
end
|
@@ -83,13 +105,14 @@ class RedisGetlock
|
|
83
105
|
payload = {uuid: uuid, expire_at: (current + expire).to_s}.to_json
|
84
106
|
if redis.setnx(key, payload) # key does not exist
|
85
107
|
redis.expire(key, expire)
|
86
|
-
|
108
|
+
return true # acquire lock
|
87
109
|
end
|
88
110
|
previous = JSON.parse(redis.get(key))
|
89
111
|
if previous['expire_at'].to_f < current # key exists, but previous
|
90
112
|
compared = redis.getset(key, paylod)
|
91
|
-
|
113
|
+
return true if previous['expire_at'] == compared['expire_at'] # acquire lock
|
92
114
|
end
|
115
|
+
return false if timeout >= 0 and (current - started) >= timeout
|
93
116
|
sleep interval
|
94
117
|
end
|
95
118
|
end
|