redlock 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ee0172835a0683dc875747ab8e69c893d63d5ad
4
+ data.tar.gz: be871d82dfeba63e0c602378fabd6e78e245fa2e
5
+ SHA512:
6
+ metadata.gz: da22d2d16562ffb6a35756bd356b2c6c27941049083596d1f5f9bb1315f3b0e0f316d1e4ae91a6b7ee5216b35c4c821f56c862080ad51f936dbb6641cb4916ec
7
+ data.tar.gz: 688aaa7b6474f3c399dbebd647b3daef565a076df5a0967a492b45227b1407ce56972d26f4bbe1ddd10104b1aaddfcc97729678b10ce11e547c3da9970d052db
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redlock_rb.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redlock (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rake (10.3.2)
11
+ rspec (3.1.0)
12
+ rspec-core (~> 3.1.0)
13
+ rspec-expectations (~> 3.1.0)
14
+ rspec-mocks (~> 3.1.0)
15
+ rspec-core (3.1.7)
16
+ rspec-support (~> 3.1.0)
17
+ rspec-expectations (3.1.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.1.0)
20
+ rspec-mocks (3.1.3)
21
+ rspec-support (~> 3.1.0)
22
+ rspec-support (3.1.2)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (~> 1.7)
29
+ rake (~> 10.0)
30
+ redlock!
31
+ rspec (~> 3.1.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
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.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # RedlockRb - A ruby distributed lock using redis.
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.
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.
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 )
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'redlock'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install redlock
24
+
25
+ ## Usage example
26
+
27
+ ```ruby
28
+ # Locking
29
+ lock_manager = Redlock::Client.new([ "redis://127.0.0.1:7777", "redis://127.0.0.1:7778", "redis://127.0.0.1:7779" ])
30
+ first_try_lock_info = lock_manager.lock("resource_key", 2000)
31
+ second_try_lock_info = lock_manager.lock("resource_key", 2000)
32
+
33
+ # it prints lock info
34
+ p first_try_lock_info
35
+ # it prints false
36
+ p second_try_lock_info
37
+
38
+ # Unlocking
39
+ lock_manager.unlock(first_try_lock_info)
40
+ second_try_lock_info = lock_manager.lock("resource_key", 2000)
41
+
42
+ # now it prints lock info
43
+ p second_try_lock_info
44
+ ```
45
+
46
+ ## Run tests
47
+
48
+ Make sure you have at least 3 redis instances `redis-server --port 777[7-9]`
49
+
50
+ $ rspec
51
+
52
+ ## Contributing
53
+
54
+ 1. Fork it ( https://github.com/leandromoreira/redlock_rb/fork )
55
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
56
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
57
+ 4. Push to the branch (`git push origin my-new-feature`)
58
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/lib/redlock.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'redlock/version'
2
+
3
+ module Redlock
4
+ autoload :Client, 'redlock/client'
5
+ end
@@ -0,0 +1,86 @@
1
+ require 'redis'
2
+ require 'securerandom'
3
+
4
+ module Redlock
5
+ class Client
6
+ DEFAULT_RETRY_COUNT = 3
7
+ DEFAULT_RETRY_DELAY = 200
8
+ CLOCK_DRIFT_FACTOR = 0.01
9
+ UNLOCK_SCRIPT = <<-eos
10
+ if redis.call("get",KEYS[1]) == ARGV[1] then
11
+ return redis.call("del",KEYS[1])
12
+ else
13
+ return 0
14
+ end
15
+ eos
16
+
17
+ # Create a distributed lock manager implementing redlock algorithm.
18
+ # Params:
19
+ # +server_urls+:: the array of redis hosts.
20
+ # +options+:: You can override the default value for `retry_count` and `retry_delay`.
21
+ # * `retry_count` being how many times it'll try to lock a resource (default: 3)
22
+ # * `retry_delay` being how many ms to sleep before try to lock again (default: 200)
23
+ def initialize(server_urls, options={})
24
+ @servers = server_urls.map {|url| Redis.new(url: url)}
25
+ @quorum = server_urls.length / 2 + 1
26
+ @retry_count = options[:retry_count] || DEFAULT_RETRY_COUNT
27
+ @retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
28
+ end
29
+
30
+ # Locks a resource for a given time. (in milliseconds)
31
+ # Params:
32
+ # +resource+:: the resource(or key) string to be locked.
33
+ # +ttl+:: The time-to-live in ms for the lock.
34
+ def lock(resource, ttl)
35
+ value = SecureRandom.uuid
36
+ @retry_count.times {
37
+ locked_instances = 0
38
+ start_time = (Time.now.to_f * 1000).to_i
39
+ @servers.each do |s|
40
+ locked_instances += 1 if lock_instance(s, resource, value, ttl)
41
+ end
42
+ # Add 2 milliseconds to the drift to account for Redis expires
43
+ # precision, which is 1 milliescond, plus 1 millisecond min drift
44
+ # for small TTLs.
45
+ drift = (ttl * CLOCK_DRIFT_FACTOR).to_i + 2
46
+ validity_time = ttl - ((Time.now.to_f * 1000).to_i - start_time) - drift
47
+ if locked_instances >= @quorum && validity_time > 0
48
+ return {
49
+ validity: validity_time,
50
+ resource: resource,
51
+ value: value
52
+ }
53
+ else
54
+ @servers.each{|s| unlock_instance(s, resource, value)}
55
+ end
56
+ # Wait a random delay before to retry
57
+ sleep(rand(@retry_delay).to_f / 1000)
58
+ }
59
+ return false
60
+ end
61
+
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])}
67
+ end
68
+
69
+ private
70
+ def lock_instance(redis, resource, val, ttl)
71
+ begin
72
+ return redis.client.call([:set, resource, val, :nx, :px, ttl])
73
+ rescue
74
+ return false
75
+ end
76
+ end
77
+
78
+ def unlock_instance(redis, resource, val)
79
+ begin
80
+ redis.client.call([:eval, UNLOCK_SCRIPT, 1, resource, val])
81
+ rescue
82
+ # Nothing to do, unlocking is just a best-effort attempt.
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module Redlock
2
+ VERSION = "0.0.1"
3
+ end
data/redlock.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'redlock/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "redlock"
8
+ spec.version = Redlock::VERSION
9
+ spec.authors = ["Leandro Moreira"]
10
+ spec.email = ["leandro.ribeiro.moreira@gmail.com"]
11
+ spec.summary = %q{Distributed lock using Redis written in Ruby.}
12
+ spec.description = %q{Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.}
13
+ spec.homepage = "https://github.com/leandromoreira/redlock-rb"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.1"
24
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ 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" }
6
+ let(:ttl) { 1000 }
7
+
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)
11
+
12
+ expect(first_try_lock_info[:resource]).to eq("foo")
13
+ expect(second_try_lock_info).to be_falsy
14
+
15
+ lock_manager.unlock(first_try_lock_info)
16
+ end
17
+
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)
22
+
23
+ expect(another_lock_info[:resource]).to eq("foo")
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ # coding: utf-8
2
+ # $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'redlock'
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redlock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Leandro Moreira
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ description: Distributed lock using Redis written in Ruby. Highly inspired by https://github.com/antirez/redlock-rb.
56
+ email:
57
+ - leandro.ribeiro.moreira@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - lib/redlock.rb
70
+ - lib/redlock/client.rb
71
+ - lib/redlock/version.rb
72
+ - redlock.gemspec
73
+ - spec/client_spec.rb
74
+ - spec/spec_helper.rb
75
+ homepage: https://github.com/leandromoreira/redlock-rb
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.2.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Distributed lock using Redis written in Ruby.
99
+ test_files:
100
+ - spec/client_spec.rb
101
+ - spec/spec_helper.rb