redlock 0.0.3 → 0.0.4

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: 982a50bb024c832b7371866687c1cb22bfe46221
4
- data.tar.gz: 63629b88159f7a268f4c6b898745870bf3580de6
3
+ metadata.gz: 0db5130e2d2570b96aa0594e0e950275900120be
4
+ data.tar.gz: a153f0211c73fe2fd20a019702db96531c446650
5
5
  SHA512:
6
- metadata.gz: 7c02c1679c769f0020236541831e8127c99d316286d082e038ef859c6ec668a98aee60d5248ae2f678e53dd0c65fb619bb8be2d609f4a7160920cdd0f0ab2428
7
- data.tar.gz: ba0dd7f8c2631dd8a6a0633cd2504827fcd678423c8a5d0a4acbee5007de9ba3b0752aba47aad16dfa383ba5c7d8d69f6a1a0e0e48f776277a6be24c623dfbbf
6
+ metadata.gz: 307248d5f5609f4829a2bc06529740912180aa4767e559b224135ace81f8414d6931ecf72bf3ada258794fa4c8d56509cc86b69b6a11c26547b9bfce6c11615e
7
+ data.tar.gz: 1bcaae57325bd013deee9e67cb21df550f589ae380d1f95ea66fbcc9a0a8faf1e04ec9aad6c99902fb3a2e274b880336679907702539765fb66c801c3cbf236c
@@ -2,7 +2,5 @@ language: ruby
2
2
  services:
3
3
  - redis-server
4
4
  rvm:
5
- - "1.9.2"
6
- - "1.9.3"
7
5
  - "2.0.0"
8
6
  script: bundle exec rspec spec
@@ -0,0 +1,11 @@
1
+ # This is the official list of people who have contributed code to
2
+ # redlock.
3
+ # You can update this list using the following command:
4
+ #
5
+ # % git shortlog -se | awk '{$1=""; print $0}' | sed -e 's/^ //'
6
+ #
7
+ # Please keep this file sorted, and group users with multiple emails.
8
+
9
+ Leandro Moreira <leandro.ribeiro.moreira@gmail.com>
10
+ Malte Rohde
11
+
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redlock (0.0.1)
4
+ redlock (0.0.4)
5
5
  redis (~> 3, >= 3.0.5)
6
6
 
7
7
  GEM
@@ -18,23 +18,24 @@ GEM
18
18
  mime-types (2.4.3)
19
19
  multi_json (1.11.0)
20
20
  netrc (0.10.3)
21
- rake (10.3.2)
21
+ rake (10.4.2)
22
22
  redis (3.2.1)
23
23
  rest-client (1.7.3)
24
24
  mime-types (>= 1.16, < 3.0)
25
25
  netrc (~> 0.7)
26
- rspec (3.1.0)
27
- rspec-core (~> 3.1.0)
28
- rspec-expectations (~> 3.1.0)
29
- rspec-mocks (~> 3.1.0)
30
- rspec-core (3.1.7)
31
- rspec-support (~> 3.1.0)
32
- rspec-expectations (3.1.2)
26
+ rspec (3.2.0)
27
+ rspec-core (~> 3.2.0)
28
+ rspec-expectations (~> 3.2.0)
29
+ rspec-mocks (~> 3.2.0)
30
+ rspec-core (3.2.2)
31
+ rspec-support (~> 3.2.0)
32
+ rspec-expectations (3.2.0)
33
33
  diff-lcs (>= 1.2.0, < 2.0)
34
- rspec-support (~> 3.1.0)
35
- rspec-mocks (3.1.3)
36
- rspec-support (~> 3.1.0)
37
- rspec-support (3.1.2)
34
+ rspec-support (~> 3.2.0)
35
+ rspec-mocks (3.2.1)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.2.0)
38
+ rspec-support (3.2.2)
38
39
  simplecov (0.9.2)
39
40
  docile (~> 1.1.0)
40
41
  multi_json (~> 1.0)
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2014-2015, Salvatore Sanfilippo <antirez at gmail dot com>
2
+ Copyright (c) 2014-2015, Leandro Moreira <leandro dot ribeiro dot moreira at gmail dot com>
3
+ Copyright (c) 2015, Malte Rohde <malte dot rohde at flavoursys dot com>
4
+
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice,
11
+ this list of conditions and the following disclaimer.
12
+
13
+ * Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
21
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md CHANGED
@@ -1,3 +1,10 @@
1
+ [![Build Status](https://travis-ci.org/leandromoreira/redlock-rb.svg?branch=master)](https://travis-ci.org/leandromoreira/redlock-rb)
2
+ [![Coverage Status](https://coveralls.io/repos/leandromoreira/redlock-rb/badge.svg?branch=master)](https://coveralls.io/r/leandromoreira/redlock-rb?branch=master)
3
+ [![Code Climate](https://codeclimate.com/github/leandromoreira/redlock-rb/badges/gpa.svg)](https://codeclimate.com/github/leandromoreira/redlock-rb)
4
+ [![Dependency Status](https://gemnasium.com/leandromoreira/redlock-rb.svg)](https://gemnasium.com/leandromoreira/redlock-rb)
5
+ [![Gem Version](https://badge.fury.io/rb/redlock.svg)](http://badge.fury.io/rb/redlock)
6
+ [![security](https://hakiri.io/github/leandromoreira/redlock-rb/master.svg)](https://hakiri.io/github/leandromoreira/redlock-rb/master)
7
+
1
8
  # Redlock - A ruby distributed lock using redis.
2
9
 
3
10
  > Distributed locks are a very useful primitive in many environments where different processes require to operate with shared resources in a mutually exclusive way.
@@ -24,7 +31,7 @@ Or install it yourself as:
24
31
 
25
32
  ## Documentation
26
33
 
27
- [RubyDoc 0.0.1](http://www.rubydoc.info/gems/redlock/0.0.1/frames)
34
+ [RubyDoc](http://www.rubydoc.info/gems/redlock/frames)
28
35
 
29
36
  ## Usage example
30
37
 
@@ -3,38 +3,34 @@ require 'securerandom'
3
3
 
4
4
  module Redlock
5
5
  class Client
6
- DEFAULT_REDIS_URLS = ['redis://localhost:6379']
7
- DEFAULT_RETRY_COUNT = 3
8
- DEFAULT_RETRY_DELAY = 200
9
- CLOCK_DRIFT_FACTOR = 0.01
10
- UNLOCK_SCRIPT = <<-eos
11
- if redis.call("get",KEYS[1]) == ARGV[1] then
12
- return redis.call("del",KEYS[1])
13
- else
14
- return 0
15
- end
16
- eos
6
+ DEFAULT_REDIS_URLS = ['redis://localhost:6379']
7
+ DEFAULT_REDIS_TIMEOUT = 0.1
8
+ DEFAULT_RETRY_COUNT = 3
9
+ DEFAULT_RETRY_DELAY = 200
10
+ CLOCK_DRIFT_FACTOR = 0.01
17
11
 
18
12
  # Create a distributed lock manager implementing redlock algorithm.
19
13
  # Params:
20
14
  # +server_urls+:: the array of redis hosts.
21
15
  # +options+:: You can override the default value for `retry_count` and `retry_delay`.
22
- # * `retry_count` being how many times it'll try to lock a resource (default: 3)
23
- # * `retry_delay` being how many ms to sleep before try to lock again (default: 200)
24
- def initialize(server_urls=DEFAULT_REDIS_URLS, options={})
25
- @servers = server_urls.map {|url| Redis.new(url: url)}
16
+ # * `retry_count` being how many times it'll try to lock a resource (default: 3)
17
+ # * `retry_delay` being how many ms to sleep before try to lock again (default: 200)
18
+ # * `redis_timeout` being how the Redis timeout will be set in seconds (default: 0.1)
19
+ def initialize(server_urls = DEFAULT_REDIS_URLS, options = {})
20
+ redis_timeout = options[:redis_timeout] || DEFAULT_REDIS_TIMEOUT
21
+ @servers = server_urls.map { |url| RedisInstance.new(url, redis_timeout) }
26
22
  @quorum = server_urls.length / 2 + 1
27
23
  @retry_count = options[:retry_count] || DEFAULT_RETRY_COUNT
28
24
  @retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
29
25
  end
30
26
 
31
- # Locks a resource for a given time. (in milliseconds)
27
+ # Locks a resource for a given time.
32
28
  # Params:
33
29
  # +resource+:: the resource (or key) string to be locked.
34
30
  # +ttl+:: The time-to-live in ms for the lock.
35
31
  # +block+:: an optional block that automatically unlocks the lock.
36
32
  def lock(resource, ttl, &block)
37
- lock_info = lock_instances(resource, ttl)
33
+ lock_info = try_lock_instances(resource, ttl)
38
34
 
39
35
  if block_given?
40
36
  begin
@@ -52,54 +48,74 @@ module Redlock
52
48
  # Params:
53
49
  # +lock_info+:: the lock that has been acquired when you locked the resource.
54
50
  def unlock(lock_info)
55
- @servers.each{|s| unlock_instance(s, lock_info[:resource], lock_info[:value])}
51
+ @servers.each { |s| s.unlock(lock_info[:resource], lock_info[:value]) }
56
52
  end
57
53
 
58
54
  private
59
55
 
60
- def lock_instances(resource, ttl)
61
- value = SecureRandom.uuid
62
- @retry_count.times do
63
- locked_instances = 0
64
- start_time = (Time.now.to_f * 1000).to_i
65
- @servers.each do |s|
66
- locked_instances += 1 if lock_instance(s, resource, value, ttl)
67
- end
68
- # Add 2 milliseconds to the drift to account for Redis expires
69
- # precision, which is 1 milliescond, plus 1 millisecond min drift
70
- # for small TTLs.
71
- drift = (ttl * CLOCK_DRIFT_FACTOR).to_i + 2
72
- validity_time = ttl - ((Time.now.to_f * 1000).to_i - start_time) - drift
73
- if locked_instances >= @quorum && validity_time > 0
74
- return {
75
- validity: validity_time,
76
- resource: resource,
77
- value: value
78
- }
56
+ class RedisInstance
57
+ UNLOCK_SCRIPT = <<-eos
58
+ if redis.call("get",KEYS[1]) == ARGV[1] then
59
+ return redis.call("del",KEYS[1])
79
60
  else
80
- @servers.each{|s| unlock_instance(s, resource, value)}
61
+ return 0
81
62
  end
82
- # Wait a random delay before to retry
83
- sleep(rand(@retry_delay).to_f / 1000)
63
+ eos
64
+
65
+ def initialize(url, timeout)
66
+ @redis = Redis.new(url: url, timeout: timeout)
84
67
  end
85
68
 
86
- return false
87
- end
69
+ def lock(resource, val, ttl)
70
+ @redis.client.call([:set, resource, val, :nx, :px, ttl])
71
+ end
88
72
 
89
- def lock_instance(redis, resource, val, ttl)
90
- begin
91
- return redis.client.call([:set, resource, val, :nx, :px, ttl])
73
+ def unlock(resource, val)
74
+ @redis.client.call([:eval, UNLOCK_SCRIPT, 1, resource, val])
92
75
  rescue
93
- return false
76
+ # Nothing to do, unlocking is just a best-effort attempt.
94
77
  end
95
78
  end
96
79
 
97
- def unlock_instance(redis, resource, val)
98
- begin
99
- redis.client.call([:eval, UNLOCK_SCRIPT, 1, resource, val])
100
- rescue
101
- # Nothing to do, unlocking is just a best-effort attempt.
80
+ def try_lock_instances(resource, ttl)
81
+ @retry_count.times do
82
+ lock_info = lock_instances(resource, ttl)
83
+ return lock_info if lock_info
84
+
85
+ # Wait a random delay before retrying
86
+ sleep(rand(@retry_delay).to_f / 1000)
102
87
  end
88
+
89
+ false
90
+ end
91
+
92
+ def lock_instances(resource, ttl)
93
+ value = SecureRandom.uuid
94
+
95
+ locked, time_elapsed = timed do
96
+ @servers.select { |s| s.lock(resource, value, ttl) }.size
97
+ end
98
+
99
+ validity = ttl - time_elapsed - drift(ttl)
100
+
101
+ if locked >= @quorum && validity >= 0
102
+ { validity: validity, resource: resource, value: value }
103
+ else
104
+ @servers.each { |s| s.unlock(resource, value) }
105
+ false
106
+ end
107
+ end
108
+
109
+ def drift(ttl)
110
+ # Add 2 milliseconds to the drift to account for Redis expires
111
+ # precision, which is 1 millisecond, plus 1 millisecond min drift
112
+ # for small TTLs.
113
+ drift = (ttl * CLOCK_DRIFT_FACTOR).to_i + 2
114
+ end
115
+
116
+ def timed
117
+ start_time = (Time.now.to_f * 1000).to_i
118
+ [yield, (Time.now.to_f * 1000).to_i - start_time]
103
119
  end
104
120
  end
105
121
  end
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = %q{Distributed lock using Redis written in Ruby.}
12
12
  spec.description = %q{Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.}
13
13
  spec.homepage = "https://github.com/leandromoreira/redlock-rb"
14
- spec.license = "MIT"
14
+ spec.license = 'BSD-2-Clause'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,89 +1,89 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redlock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Moreira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-19 00:00:00.000000000 Z
11
+ date: 2015-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3'
20
- - - '>='
20
+ - - ">="
21
21
  - !ruby/object:Gem::Version
22
22
  version: 3.0.5
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ~>
27
+ - - "~>"
28
28
  - !ruby/object:Gem::Version
29
29
  version: '3'
30
- - - '>='
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 3.0.5
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: bundler
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ~>
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.7'
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ~>
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '1.7'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: coveralls
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - '>='
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - '>='
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rake
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ~>
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '10.0'
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ~>
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: '10.0'
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rspec
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - ~>
79
+ - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '3.1'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - ~>
86
+ - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: '3.1'
89
89
  description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
@@ -93,12 +93,13 @@ executables: []
93
93
  extensions: []
94
94
  extra_rdoc_files: []
95
95
  files:
96
- - .gitignore
97
- - .rspec
98
- - .travis.yml
96
+ - ".gitignore"
97
+ - ".rspec"
98
+ - ".travis.yml"
99
+ - CONTRIBUTORS
99
100
  - Gemfile
100
101
  - Gemfile.lock
101
- - LICENSE.txt
102
+ - LICENSE
102
103
  - README.md
103
104
  - Rakefile
104
105
  - lib/redlock.rb
@@ -109,7 +110,7 @@ files:
109
110
  - spec/spec_helper.rb
110
111
  homepage: https://github.com/leandromoreira/redlock-rb
111
112
  licenses:
112
- - MIT
113
+ - BSD-2-Clause
113
114
  metadata: {}
114
115
  post_install_message:
115
116
  rdoc_options: []
@@ -117,17 +118,17 @@ require_paths:
117
118
  - lib
118
119
  required_ruby_version: !ruby/object:Gem::Requirement
119
120
  requirements:
120
- - - '>='
121
+ - - ">="
121
122
  - !ruby/object:Gem::Version
122
123
  version: '0'
123
124
  required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  requirements:
125
- - - '>='
126
+ - - ">="
126
127
  - !ruby/object:Gem::Version
127
128
  version: '0'
128
129
  requirements: []
129
130
  rubyforge_project:
130
- rubygems_version: 2.0.14
131
+ rubygems_version: 2.2.2
131
132
  signing_key:
132
133
  specification_version: 4
133
134
  summary: Distributed lock using Redis written in Ruby.
@@ -1,22 +0,0 @@
1
- Copyright (c) 2014 Leandro Moreira
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.