redlock-rb 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28f6b1a1bf9dfa26e2eb3c170a045b2c287f0315
4
+ data.tar.gz: 63aadbdf39a22a30e4c3ca2265a1ee4115fd7d0f
5
+ SHA512:
6
+ metadata.gz: c32cf442e6174f4aa37793802b167d206df859ed38b8b8614bc9b17ac0273b0436f2a75becd53637ddd686b9fbba2afcbf5b1a43f7dd3d6da80c58dbbd31958c
7
+ data.tar.gz: 147e85613e2fcf8c5ab32d14dd5a64cc8a3cf029f400ea372d25ba42ee585a8bff5b81e61b8bae0a731314609590df1f2b651ebf414a33dac1ac397ae04f4921
@@ -0,0 +1,4 @@
1
+ .ruby-version
2
+ .ruby-gemset
3
+ Gemfile.lock
4
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in redlock.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Salvatore Sanfilippo
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.
@@ -0,0 +1,35 @@
1
+ Redlock-rb - Redis distributed locks in Ruby
2
+
3
+ This Ruby lib implements the Redis-based distributed lock manager algorithm [described in this blog post](http://antirez.com/news/77).
4
+
5
+ To create a lock manager:
6
+
7
+ dlm = Redlock.new("redis://127.0.0.1:6379","redis://127.0.0.1:6380","redis://127.0.0.1:6381")
8
+
9
+ To acquire a lock:
10
+
11
+ my_lock = dlm.lock("my_resource_name",1000)
12
+
13
+ Where the resource name is an unique identifier of what you are trying to lock
14
+ and 1000 is the number of milliseconds for the validity time.
15
+
16
+ The returned value is `false` if the lock was not acquired (you may try again),
17
+ otherwise an hash representing the lock is returned, having three fields:
18
+
19
+ * :validity, an integer representing the number of milliseconds the lock will be valid.
20
+ * :resource, the name of the locked resource as specified by the user.
21
+ * :val, a random value which is used to safe reclaim the lock.
22
+
23
+ To release a lock:
24
+
25
+ dlm.unlock(my_lock)
26
+
27
+ It is possible to setup the number of retries (by default 3) and the retry
28
+ delay (by default 200 milliseconds) used to acquire the lock. The retry is
29
+ actually choosen at random between 0 milliseconds and the specified value.
30
+ The `set_retry` method can be used in order to configure the retry count
31
+ and delay range:
32
+
33
+ dlm.set_retry(3,200)
34
+
35
+ **Disclaimer**: This code implements an algorithm which is currently a proposal, it was not formally analyzed. Make sure to understand how it works before using it in your production environments.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,13 @@
1
+ require './redlock.rb'
2
+
3
+ dlm = Redlock.new("redis://127.0.0.1:6379","redis://127.0.0.1:6380","redis://127.0.0.1:6381")
4
+
5
+ while 1
6
+ my_lock = dlm.lock("foo",1000)
7
+ if my_lock
8
+ puts "Acquired by client #{dlm}"
9
+ dlm.unlock(my_lock)
10
+ else
11
+ puts "Error, lock not acquired"
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ require './redlock.rb'
2
+
3
+ def thread_main(count)
4
+ dlm = Redlock.new("redis://127.0.0.1:6379","redis://127.0.0.1:6380","redis://127.0.0.1:6381")
5
+
6
+ incr=0
7
+ count.times {
8
+ my_lock = dlm.lock("foo",1000)
9
+ if my_lock
10
+ if my_lock[:validity] > 500
11
+ # Note: we assume we can do it in 500 milliseconds. If this
12
+ # assumption is not correct, the program output will not be
13
+ # correct.
14
+ number = File.read("/tmp/counter.txt")
15
+ File.write("/tmp/counter.txt",(number.to_i+1).to_s)
16
+ incr += 1
17
+ end
18
+ dlm.unlock(my_lock)
19
+ end
20
+ }
21
+ puts "/tmp/counter.txt incremented #{incr} times."
22
+ end
23
+
24
+ File.write("/tmp/counter.txt","0")
25
+ threads=[]
26
+ 5.times {
27
+ threads << Thread.new{thread_main(100)}
28
+ }
29
+ threads.each{|t| t.join}
30
+ puts "Counter value is #{File.read("/tmp/counter.txt")}"
@@ -0,0 +1 @@
1
+ require 'redlock'
@@ -0,0 +1,114 @@
1
+ require 'redlock/version'
2
+ require 'redis'
3
+
4
+ class Redlock
5
+ DEFAULT_RETRY_COUNT = 3
6
+ DEFAULT_RETRY_DELAY = 200
7
+ CLOCK_DRIFT_FACTOR = 0.01
8
+ UNLOCK_SCRIPT = '
9
+ if redis.call("get",KEYS[1]) == ARGV[1] then
10
+ return redis.call("del",KEYS[1])
11
+ else
12
+ return 0
13
+ end'
14
+
15
+ def initialize(*server_urls)
16
+ @servers = []
17
+ server_urls.each{|url|
18
+ @servers << Redis.new(:url => url)
19
+ }
20
+ @quorum = server_urls.length / 2 + 1
21
+ @retry_count = DEFAULT_RETRY_COUNT
22
+ @retry_delay = DEFAULT_RETRY_DELAY
23
+ load_script
24
+ end
25
+
26
+ def load_script
27
+ @servers.each do |server|
28
+ @unlock_sha = server.script(:load, UNLOCK_SCRIPT)
29
+ end
30
+ end
31
+
32
+ def set_retry(count,delay)
33
+ @retry_count = count
34
+ @retry_delay = delay
35
+ end
36
+
37
+ def testing=(mode)
38
+ @testing_mode = mode
39
+ end
40
+
41
+ def lock_instance(redis,resource,val,ttl)
42
+ begin
43
+ return redis.set(resource, val, nx: true, px: ttl)
44
+ rescue
45
+ return false
46
+ end
47
+ end
48
+
49
+ def unlock_instance(redis,resource,val)
50
+ begin
51
+ redis.evalsha(@unlock_sha, keys: [resource], argv: [val])
52
+ rescue
53
+ # Nothing to do, unlocking is just a best-effort attempt.
54
+ end
55
+ end
56
+
57
+ def get_unique_lock_id
58
+ val = ""
59
+ bytes = SecureRandom.random_bytes(20)
60
+ bytes.each_byte{|b|
61
+ val << b.to_s(32)
62
+ }
63
+ val
64
+ end
65
+
66
+ def lock(resource,ttl,val=nil)
67
+ val = get_unique_lock_id if val.nil?
68
+
69
+ if @testing_mode == :bypass
70
+ return {
71
+ validity: ttl,
72
+ resource: resource,
73
+ val: val
74
+ }
75
+ elsif @testing_mode == :fail
76
+ return false
77
+ end
78
+
79
+ @retry_count.times {
80
+ n = 0
81
+ start_time = (Time.now.to_f*1000).to_i
82
+ @servers.each{|s|
83
+ n += 1 if lock_instance(s,resource,val,ttl)
84
+ }
85
+ # Add 2 milliseconds to the drift to account for Redis expires
86
+ # precision, which is 1 milliescond, plus 1 millisecond min drift
87
+ # for small TTLs.
88
+ drift = (ttl*CLOCK_DRIFT_FACTOR).to_i + 2
89
+ validity_time = ttl-((Time.now.to_f*1000).to_i - start_time)-drift
90
+ if n >= @quorum && validity_time > 0
91
+ return {
92
+ :validity => validity_time,
93
+ :resource => resource,
94
+ :val => val
95
+ }
96
+ else
97
+ @servers.each{|s|
98
+ unlock_instance(s,resource,val)
99
+ }
100
+ end
101
+ # Wait a random delay before to retry
102
+ sleep(rand(@retry_delay).to_f/1000)
103
+ }
104
+ return false
105
+ end
106
+
107
+ def unlock(lock)
108
+ return if @testing_mode == :bypass
109
+
110
+ @servers.each{|s|
111
+ unlock_instance(s,lock[:resource],lock[:val])
112
+ }
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ class Redlock
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,25 @@
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-rb"
8
+ spec.version = Redlock::VERSION
9
+ spec.authors = ["Salvatore Sanfilippo", "Ryan Schlesinger"]
10
+ spec.email = ["antirez@gmail.com", "ryan@aceofsales.com"]
11
+ spec.summary = %q{Redlock Redis-based distributed locks implementation in Ruby}
12
+ spec.homepage = "https://github.com/aceofsales/redlock-rb"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+
23
+ # redis-rb 3.0.0 is when scripting support dropped
24
+ spec.add_dependency 'redis', '~> 3.0'
25
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redlock-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Salvatore Sanfilippo
8
+ - Ryan Schlesinger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.6'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.6'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: redis
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ description:
57
+ email:
58
+ - antirez@gmail.com
59
+ - ryan@aceofsales.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - examples/example.rb
70
+ - examples/example2.rb
71
+ - lib/redlock-rb.rb
72
+ - lib/redlock.rb
73
+ - lib/redlock/version.rb
74
+ - redlock-rb.gemspec
75
+ homepage: https://github.com/aceofsales/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.4.3
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Redlock Redis-based distributed locks implementation in Ruby
99
+ test_files: []