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 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