redlock 0.0.1 → 0.0.2

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