redis_getlock 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|