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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9c0826498386afb3440ad4c10e7ce2b984a6eac2
4
- data.tar.gz: f52a7bc3a8d04316bb3985025ca859b4f703885b
3
+ metadata.gz: e211a307e1814231fd3bff20fe5a435c4086bd4b
4
+ data.tar.gz: 8eff81ba4ddc1bdbd434cc730c676684b3b6a24f
5
5
  SHA512:
6
- metadata.gz: d5a15c9c815fb0c2a7b8fe586bbe5985b17aab40b8f0193a9098e5345e396ec1b440e7776c3e6eec9230296304130b748b5f2f8a4560c44a42f4b8f5c037faec
7
- data.tar.gz: 950bcb6a8643fdb4470c9963389d11bbe2c9d275bb53567f997ad25787619396474025ca4c9a4a95a20dae51db718b35833d636162c5056ada289ac110d02f1e
6
+ metadata.gz: c5066ccec8e8b53c5b8f8a58c0d80740921a7627431918ed2a92d3533620c6fa1787c72d3aeaa7b6f0ce6899b434ddd236b14ee1c3c87435e357b1890040bf34
7
+ data.tar.gz: 77b1d0ba859c9e7b0dd04ee59473c9aefc2e785ca03a131902c342da4c6418eee9646f7b0626b38e2bc821ad8ca6560a8c7f087cae1f10be4655d9f20a327e6f
@@ -1,3 +1,9 @@
1
+ # 0.3.1 (2016-08-29)
2
+
3
+ Enhancements:
4
+
5
+ * Add timeout option
6
+
1
7
  # 0.3.0 (2016-08-28)
2
8
 
3
9
  Enhancements:
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) sencond interval invoking another thread
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
- if ARGV[0] == 'exit!'
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(redis: Redis.new, key: 'redis_getlock', logger: Logger.new(STDOUT))
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
@@ -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
- def initialize(redis:, key:, logger: nil, expire: EXPIRE, interval: INTERVAL)
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 acquiring a redis lock '#{key}'" } if logger
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
- lock_without_set_options
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
- redis.del(key)
35
- logger.info { "#{log_head}Released a redis lock '#{key}'" } if logger
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
- redis.exists(key) && uuid == JSON.parse(redis.get(key))['uuid']
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
- break if redis.set(key, payload, {nx: true, ex: expire}) # key does not exist
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
- break # acquire lock
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
- break if previous['expire_at'] == compared['expire_at'] # acquire lock
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
@@ -1,3 +1,3 @@
1
1
  class RedisGetlock
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_getlock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Naotoshi Seo