redlock 0.0.1 → 0.0.2

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: 3ee0172835a0683dc875747ab8e69c893d63d5ad
4
- data.tar.gz: be871d82dfeba63e0c602378fabd6e78e245fa2e
3
+ metadata.gz: 89b482115278216ae9e620ce8f7430bf4fe36715
4
+ data.tar.gz: 75f2da7d8d167fea72815ca9e4ea3242425dfd9a
5
5
  SHA512:
6
- metadata.gz: da22d2d16562ffb6a35756bd356b2c6c27941049083596d1f5f9bb1315f3b0e0f316d1e4ae91a6b7ee5216b35c4c821f56c862080ad51f936dbb6641cb4916ec
7
- data.tar.gz: 688aaa7b6474f3c399dbebd647b3daef565a076df5a0967a492b45227b1407ce56972d26f4bbe1ddd10104b1aaddfcc97729678b10ce11e547c3da9970d052db
6
+ metadata.gz: 33c9ef53d6833e461ac6c805748fcf20923f29e2fde681c684992cd2ff2fe607d84b5e17b95515331470bce0aa39a7662e5f5dc7a83941a17c9b647072145742
7
+ data.tar.gz: 068c9b13201e315d9060ed6e646f1e19a6371a0892ece0d9f2fd719d45dee65aa622324a25d06a290e5e56638079f91da58d97fb2b214eea0c2230c185cc6ca4
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  *.gem
2
+ coverage/
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ services:
3
+ - redis-server
4
+ rvm:
5
+ - "1.9.2"
6
+ - "1.9.3"
7
+ - "2.0.0"
8
+ script: bundle exec rspec spec
data/Gemfile.lock CHANGED
@@ -2,12 +2,27 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  redlock (0.0.1)
5
+ redis (~> 3, >= 3.0.5)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ coveralls (0.7.11)
11
+ multi_json (~> 1.10)
12
+ rest-client (>= 1.6.8, < 2)
13
+ simplecov (~> 0.9.1)
14
+ term-ansicolor (~> 1.3)
15
+ thor (~> 0.19.1)
9
16
  diff-lcs (1.2.5)
17
+ docile (1.1.5)
18
+ mime-types (2.4.3)
19
+ multi_json (1.11.0)
20
+ netrc (0.10.3)
10
21
  rake (10.3.2)
22
+ redis (3.2.1)
23
+ rest-client (1.7.3)
24
+ mime-types (>= 1.16, < 3.0)
25
+ netrc (~> 0.7)
11
26
  rspec (3.1.0)
12
27
  rspec-core (~> 3.1.0)
13
28
  rspec-expectations (~> 3.1.0)
@@ -20,12 +35,22 @@ GEM
20
35
  rspec-mocks (3.1.3)
21
36
  rspec-support (~> 3.1.0)
22
37
  rspec-support (3.1.2)
38
+ simplecov (0.9.2)
39
+ docile (~> 1.1.0)
40
+ multi_json (~> 1.0)
41
+ simplecov-html (~> 0.9.0)
42
+ simplecov-html (0.9.0)
43
+ term-ansicolor (1.3.0)
44
+ tins (~> 1.0)
45
+ thor (0.19.1)
46
+ tins (1.3.5)
23
47
 
24
48
  PLATFORMS
25
49
  ruby
26
50
 
27
51
  DEPENDENCIES
28
52
  bundler (~> 1.7)
53
+ coveralls
29
54
  rake (~> 10.0)
30
55
  redlock!
31
- rspec (~> 3.1.0)
56
+ rspec (~> 3.1)
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # RedlockRb - A ruby distributed lock using redis.
1
+ # Redlock - A ruby distributed lock using redis.
2
2
 
3
- Distributed locks are a very useful primitive in many environments where different processes require to operate with shared resources in a mutually exclusive way.
3
+ > Distributed locks are a very useful primitive in many environments where different processes require to operate with shared resources in a mutually exclusive way.
4
+ >
5
+ > There are a number of libraries and blog posts describing how to implement a DLM (Distributed Lock Manager) with Redis, but every library uses a different approach, and many use a simple approach with lower guarantees compared to what can be achieved with slightly more complex designs.
4
6
 
5
- There are a number of libraries and blog posts describing how to implement a DLM (Distributed Lock Manager) with Redis, but every library uses a different approach, and many use a simple approach with lower guarantees compared to what can be achieved with slightly more complex designs.
6
-
7
- This lib is an attempt to provide an implementation to a proposed distributed locks with Redis. Totally inspired by: ( http://redis.io/topics/distlock )
7
+ This is an implementation of a proposed [distributed lock algorithm with Redis](http://redis.io/topics/distlock). It started as a fork from [antirez implementation.](https://github.com/antirez/redlock-rb)
8
8
 
9
9
  ## Installation
10
10
 
@@ -22,6 +22,10 @@ Or install it yourself as:
22
22
 
23
23
  $ gem install redlock
24
24
 
25
+ ## Documentation
26
+
27
+ [RubyDoc 0.0.1](http://www.rubydoc.info/gems/redlock/0.0.1/frames)
28
+
25
29
  ## Usage example
26
30
 
27
31
  ```ruby
@@ -30,7 +34,7 @@ Or install it yourself as:
30
34
  first_try_lock_info = lock_manager.lock("resource_key", 2000)
31
35
  second_try_lock_info = lock_manager.lock("resource_key", 2000)
32
36
 
33
- # it prints lock info
37
+ # it prints lock info {validity: 1987, resource: "resource_key", value: "generated_uuid4"}
34
38
  p first_try_lock_info
35
39
  # it prints false
36
40
  p second_try_lock_info
@@ -43,6 +47,18 @@ Or install it yourself as:
43
47
  p second_try_lock_info
44
48
  ```
45
49
 
50
+ There's also a block version that automatically unlocks the lock:
51
+
52
+ ```ruby
53
+ lock_manager.lock("resource_key", 2000) do |locked|
54
+ if locked
55
+ # critical code
56
+ else
57
+ # error handling
58
+ end
59
+ end
60
+ ```
61
+
46
62
  ## Run tests
47
63
 
48
64
  Make sure you have at least 3 redis instances `redis-server --port 777[7-9]`
@@ -51,7 +67,7 @@ Make sure you have at least 3 redis instances `redis-server --port 777[7-9]`
51
67
 
52
68
  ## Contributing
53
69
 
54
- 1. Fork it ( https://github.com/leandromoreira/redlock_rb/fork )
70
+ 1. [Fork it](https://github.com/leandromoreira/redlock-rb/fork)
55
71
  2. Create your feature branch (`git checkout -b my-new-feature`)
56
72
  3. Commit your changes (`git commit -am 'Add some feature'`)
57
73
  4. Push to the branch (`git push origin my-new-feature`)
@@ -3,6 +3,7 @@ require 'securerandom'
3
3
 
4
4
  module Redlock
5
5
  class Client
6
+ DEFAULT_REDIS_URLS = ['redis://localhost:6379']
6
7
  DEFAULT_RETRY_COUNT = 3
7
8
  DEFAULT_RETRY_DELAY = 200
8
9
  CLOCK_DRIFT_FACTOR = 0.01
@@ -20,7 +21,7 @@ module Redlock
20
21
  # +options+:: You can override the default value for `retry_count` and `retry_delay`.
21
22
  # * `retry_count` being how many times it'll try to lock a resource (default: 3)
22
23
  # * `retry_delay` being how many ms to sleep before try to lock again (default: 200)
23
- def initialize(server_urls, options={})
24
+ def initialize(server_urls=DEFAULT_REDIS_URLS, options={})
24
25
  @servers = server_urls.map {|url| Redis.new(url: url)}
25
26
  @quorum = server_urls.length / 2 + 1
26
27
  @retry_count = options[:retry_count] || DEFAULT_RETRY_COUNT
@@ -29,11 +30,35 @@ module Redlock
29
30
 
30
31
  # Locks a resource for a given time. (in milliseconds)
31
32
  # Params:
32
- # +resource+:: the resource(or key) string to be locked.
33
+ # +resource+:: the resource (or key) string to be locked.
33
34
  # +ttl+:: The time-to-live in ms for the lock.
34
- def lock(resource, ttl)
35
+ def lock(resource, ttl, &block)
36
+ lock_info = lock_instances(resource, ttl)
37
+
38
+ if block_given?
39
+ begin
40
+ yield lock_info
41
+ !!lock_info
42
+ ensure
43
+ unlock(lock_info) if lock_info
44
+ end
45
+ else
46
+ lock_info
47
+ end
48
+ end
49
+
50
+ # Unlocks a resource.
51
+ # Params:
52
+ # +lock_info+:: the lock that has been acquired when you locked the resource.
53
+ def unlock(lock_info)
54
+ @servers.each{|s| unlock_instance(s, lock_info[:resource], lock_info[:value])}
55
+ end
56
+
57
+ private
58
+
59
+ def lock_instances(resource, ttl)
35
60
  value = SecureRandom.uuid
36
- @retry_count.times {
61
+ @retry_count.times do
37
62
  locked_instances = 0
38
63
  start_time = (Time.now.to_f * 1000).to_i
39
64
  @servers.each do |s|
@@ -55,18 +80,11 @@ module Redlock
55
80
  end
56
81
  # Wait a random delay before to retry
57
82
  sleep(rand(@retry_delay).to_f / 1000)
58
- }
59
- return false
60
- end
83
+ end
61
84
 
62
- # Unlocks a resource.
63
- # Params:
64
- # +lock_info+:: the has acquired when you locked the resource.
65
- def unlock(lock_info)
66
- @servers.each{|s| unlock_instance(s, lock_info[:resource], lock_info[:value])}
85
+ return false
67
86
  end
68
87
 
69
- private
70
88
  def lock_instance(redis, resource, val, ttl)
71
89
  begin
72
90
  return redis.client.call([:set, resource, val, :nx, :px, ttl])
@@ -1,3 +1,3 @@
1
1
  module Redlock
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/redlock.gemspec CHANGED
@@ -18,7 +18,10 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.add_dependency 'redis', '~> 3', '>= 3.0.5'
22
+
21
23
  spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "coveralls"
22
25
  spec.add_development_dependency "rake", "~> 10.0"
23
26
  spec.add_development_dependency "rspec", "~> 3.1"
24
27
  end
data/spec/client_spec.rb CHANGED
@@ -1,25 +1,97 @@
1
1
  require 'spec_helper'
2
+ require 'securerandom'
2
3
 
3
4
  RSpec.describe Redlock::Client do
4
- let(:lock_manager) { Redlock::Client.new([ "redis://127.0.0.1:7777", "redis://127.0.0.1:7778", "redis://127.0.0.1:7779" ]) }
5
- let(:resource_key) { "foo" }
5
+ # It is recommended to have at least 3 servers in production
6
+ let(:lock_manager) { Redlock::Client.new }
7
+ let(:resource_key) { SecureRandom.hex(3) }
6
8
  let(:ttl) { 1000 }
7
9
 
8
- it 'locks' do
9
- first_try_lock_info = lock_manager.lock(resource_key, ttl)
10
- second_try_lock_info = lock_manager.lock(resource_key, ttl)
10
+ describe 'lock' do
11
+ context 'when lock is available' do
12
+ after(:each) { lock_manager.unlock(@lock_info) if @lock_info }
11
13
 
12
- expect(first_try_lock_info[:resource]).to eq("foo")
13
- expect(second_try_lock_info).to be_falsy
14
+ it 'locks' do
15
+ @lock_info = lock_manager.lock(resource_key, ttl)
14
16
 
15
- lock_manager.unlock(first_try_lock_info)
17
+ expect(resource_key).to_not be_lockable(lock_manager, ttl)
18
+ end
19
+
20
+ it 'returns lock information' do
21
+ @lock_info = lock_manager.lock(resource_key, ttl)
22
+
23
+ expect(@lock_info).to be_lock_info_for(resource_key)
24
+ end
25
+ end
26
+
27
+ context 'when lock is not available' do
28
+ before { @another_lock_info = lock_manager.lock(resource_key, ttl) }
29
+ after { lock_manager.unlock(@another_lock_info) }
30
+
31
+ it 'returns false' do
32
+ lock_info = lock_manager.lock(resource_key, ttl)
33
+
34
+ expect(lock_info).to eql(false)
35
+ end
36
+ end
37
+
38
+ describe 'block syntax' do
39
+ context 'when lock is available' do
40
+ it 'locks' do
41
+ lock_manager.lock(resource_key, ttl) do |_|
42
+ expect(resource_key).to_not be_lockable(lock_manager, ttl)
43
+ end
44
+ end
45
+
46
+ it 'passes lock information as block argument' do
47
+ lock_manager.lock(resource_key, ttl) do |lock_info|
48
+ expect(lock_info).to be_lock_info_for(resource_key)
49
+ end
50
+ end
51
+
52
+ it 'returns true' do
53
+ rv = lock_manager.lock(resource_key, ttl) {}
54
+ expect(rv).to eql(true)
55
+ end
56
+
57
+ it 'automatically unlocks' do
58
+ lock_manager.lock(resource_key, ttl) {}
59
+ expect(resource_key).to be_lockable(lock_manager, ttl)
60
+ end
61
+
62
+ it 'automatically unlocks when block raises exception' do
63
+ lock_manager.lock(resource_key, ttl) { fail } rescue nil
64
+ expect(resource_key).to be_lockable(lock_manager, ttl)
65
+ end
66
+ end
67
+
68
+ context 'when lock is not available' do
69
+ before { @another_lock_info = lock_manager.lock(resource_key, ttl) }
70
+ after { lock_manager.unlock(@another_lock_info) }
71
+
72
+ it 'passes false as block argument' do
73
+ lock_manager.lock(resource_key, ttl) do |lock_info|
74
+ expect(lock_info).to eql(false)
75
+ end
76
+ end
77
+
78
+ it 'returns false' do
79
+ rv = lock_manager.lock(resource_key, ttl) {}
80
+ expect(rv).to eql(false)
81
+ end
82
+ end
83
+ end
16
84
  end
17
85
 
18
- it 'unlocks' do
19
- lock_info = lock_manager.lock(resource_key, ttl)
20
- lock_manager.unlock(lock_info)
21
- another_lock_info = lock_manager.lock(resource_key, ttl)
86
+ describe 'unlock' do
87
+ before { @lock_info = lock_manager.lock(resource_key, ttl) }
88
+
89
+ it 'unlocks' do
90
+ expect(resource_key).to_not be_lockable(lock_manager, ttl)
91
+
92
+ lock_manager.unlock(@lock_info)
22
93
 
23
- expect(another_lock_info[:resource]).to eq("foo")
94
+ expect(resource_key).to be_lockable(lock_manager, ttl)
95
+ end
24
96
  end
25
97
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,44 @@
1
1
  # coding: utf-8
2
2
  # $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'coveralls'
4
+ Coveralls.wear!
3
5
  require 'redlock'
6
+
7
+ LOCK_INFO_KEYS = %i{validity resource value}
8
+
9
+ RSpec::Matchers.define :be_lock_info_for do |resource|
10
+ def correct_type?(actual)
11
+ actual.is_a?(Hash)
12
+ end
13
+
14
+ def correct_layout?(actual)
15
+ ((LOCK_INFO_KEYS | actual.keys) - (LOCK_INFO_KEYS & actual.keys)).empty?
16
+ end
17
+
18
+ def correct_resource?(actual, resource)
19
+ actual[:resource] == resource
20
+ end
21
+
22
+ match do |actual|
23
+ correct_type?(actual) && correct_layout?(actual) && correct_resource?(actual, resource)
24
+ end
25
+
26
+ failure_message do |actual|
27
+ "expected that #{actual} would be lock information for #{expected}"
28
+ end
29
+ end
30
+
31
+ RSpec::Matchers.define :be_lockable do |lock_manager, ttl|
32
+ match do |resource_key|
33
+ begin
34
+ lock_info = lock_manager.lock(resource_key, ttl)
35
+ lock_info != false
36
+ ensure
37
+ lock_manager.unlock(lock_info) if lock_info
38
+ end
39
+ end
40
+
41
+ failure_message do |resource_key|
42
+ "expected that #{resource_key} would be lockable"
43
+ end
44
+ end
metadata CHANGED
@@ -1,55 +1,89 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redlock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Moreira
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-13 00:00:00.000000000 Z
11
+ date: 2015-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ - - '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.5
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: bundler
15
35
  requirement: !ruby/object:Gem::Requirement
16
36
  requirements:
17
- - - "~>"
37
+ - - ~>
18
38
  - !ruby/object:Gem::Version
19
39
  version: '1.7'
20
40
  type: :development
21
41
  prerelease: false
22
42
  version_requirements: !ruby/object:Gem::Requirement
23
43
  requirements:
24
- - - "~>"
44
+ - - ~>
25
45
  - !ruby/object:Gem::Version
26
46
  version: '1.7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: coveralls
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
27
61
  - !ruby/object:Gem::Dependency
28
62
  name: rake
29
63
  requirement: !ruby/object:Gem::Requirement
30
64
  requirements:
31
- - - "~>"
65
+ - - ~>
32
66
  - !ruby/object:Gem::Version
33
67
  version: '10.0'
34
68
  type: :development
35
69
  prerelease: false
36
70
  version_requirements: !ruby/object:Gem::Requirement
37
71
  requirements:
38
- - - "~>"
72
+ - - ~>
39
73
  - !ruby/object:Gem::Version
40
74
  version: '10.0'
41
75
  - !ruby/object:Gem::Dependency
42
76
  name: rspec
43
77
  requirement: !ruby/object:Gem::Requirement
44
78
  requirements:
45
- - - "~>"
79
+ - - ~>
46
80
  - !ruby/object:Gem::Version
47
81
  version: '3.1'
48
82
  type: :development
49
83
  prerelease: false
50
84
  version_requirements: !ruby/object:Gem::Requirement
51
85
  requirements:
52
- - - "~>"
86
+ - - ~>
53
87
  - !ruby/object:Gem::Version
54
88
  version: '3.1'
55
89
  description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
@@ -59,8 +93,9 @@ executables: []
59
93
  extensions: []
60
94
  extra_rdoc_files: []
61
95
  files:
62
- - ".gitignore"
63
- - ".rspec"
96
+ - .gitignore
97
+ - .rspec
98
+ - .travis.yml
64
99
  - Gemfile
65
100
  - Gemfile.lock
66
101
  - LICENSE.txt
@@ -82,17 +117,17 @@ require_paths:
82
117
  - lib
83
118
  required_ruby_version: !ruby/object:Gem::Requirement
84
119
  requirements:
85
- - - ">="
120
+ - - '>='
86
121
  - !ruby/object:Gem::Version
87
122
  version: '0'
88
123
  required_rubygems_version: !ruby/object:Gem::Requirement
89
124
  requirements:
90
- - - ">="
125
+ - - '>='
91
126
  - !ruby/object:Gem::Version
92
127
  version: '0'
93
128
  requirements: []
94
129
  rubyforge_project:
95
- rubygems_version: 2.2.2
130
+ rubygems_version: 2.0.14
96
131
  signing_key:
97
132
  specification_version: 4
98
133
  summary: Distributed lock using Redis written in Ruby.