redlock 0.0.3 → 0.0.4

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