ace_redlock 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9fe3c2c4c7dc91071751ba9f4987724c215d381a
4
+ data.tar.gz: 54ec078469650fd1a3c30ef977f5bba733d8646c
5
+ SHA512:
6
+ metadata.gz: c926716e26801fefa2b51afbc7dec0c3451040fbc5dbaf388c489ea6235ce41ad455b3912f42f85495b823059ef573f3dd56aa0bb7b54b5a20cdcd51deaeed57
7
+ data.tar.gz: 492cffcfb7514946d4f65e8e5983bdcb33fcf03cd9a3f0aad56b2d293a14706a0f030038fc1d5011f0132e35ca12f0162d6d842b77624b7f9aee5e64faaa2eac
data/.gitignore ADDED
@@ -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
data/LICENSE.txt ADDED
@@ -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.
data/README.md ADDED
@@ -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.
data/Rakefile ADDED
@@ -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'
data/lib/redlock.rb ADDED
@@ -0,0 +1,96 @@
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
+ end
24
+
25
+ def load_script
26
+ @servers.each do |server|
27
+ @unlock_sha = server.script(:load, UNLOCK_SCRIPT)
28
+ end
29
+ end
30
+
31
+ def set_retry(count,delay)
32
+ @retry_count = count
33
+ @retry_delay = delay
34
+ end
35
+
36
+ def lock_instance(redis,resource,val,ttl)
37
+ begin
38
+ return redis.set(resource, val, nx: true, px: ttl)
39
+ rescue
40
+ return false
41
+ end
42
+ end
43
+
44
+ def unlock_instance(redis,resource,val)
45
+ begin
46
+ redis.evalsha(@unlock_sha, keys: [resource], argv: [val])
47
+ rescue
48
+ # Nothing to do, unlocking is just a best-effort attempt.
49
+ end
50
+ end
51
+
52
+ def get_unique_lock_id
53
+ val = ""
54
+ bytes = SecureRandom.random_bytes(20)
55
+ bytes.each_byte{|b|
56
+ val << b.to_s(32)
57
+ }
58
+ val
59
+ end
60
+
61
+ def lock(resource,ttl,val=nil)
62
+ val = get_unique_lock_id if val.nil?
63
+ @retry_count.times {
64
+ n = 0
65
+ start_time = (Time.now.to_f*1000).to_i
66
+ @servers.each{|s|
67
+ n += 1 if lock_instance(s,resource,val,ttl)
68
+ }
69
+ # Add 2 milliseconds to the drift to account for Redis expires
70
+ # precision, which is 1 milliescond, plus 1 millisecond min drift
71
+ # for small TTLs.
72
+ drift = (ttl*CLOCK_DRIFT_FACTOR).to_i + 2
73
+ validity_time = ttl-((Time.now.to_f*1000).to_i - start_time)-drift
74
+ if n >= @quorum && validity_time > 0
75
+ return {
76
+ :validity => validity_time,
77
+ :resource => resource,
78
+ :val => val
79
+ }
80
+ else
81
+ @servers.each{|s|
82
+ unlock_instance(s,resource,val)
83
+ }
84
+ end
85
+ # Wait a random delay before to retry
86
+ sleep(rand(@retry_delay).to_f/1000)
87
+ }
88
+ return false
89
+ end
90
+
91
+ def unlock(lock)
92
+ @servers.each{|s|
93
+ unlock_instance(s,lock[:resource],lock[:val])
94
+ }
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ class Redlock
2
+ VERSION = '0.0.2'
3
+ end
data/redlock.gemspec ADDED
@@ -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 = "ace_redlock"
8
+ spec.version = Redlock::VERSION
9
+ spec.authors = ["Salvatore Sanfilippo", "Ryan Schlesinger"]
10
+ spec.email = ["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,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ace_redlock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Salvatore Sanfilippo
8
+ - Ryan Schlesinger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-09-18 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
+ - ryan@aceofsales.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - examples/example.rb
69
+ - examples/example2.rb
70
+ - lib/ace_redlock.rb
71
+ - lib/redlock.rb
72
+ - lib/redlock/version.rb
73
+ - redlock.gemspec
74
+ homepage: https://github.com/aceofsales/redlock-rb
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.2.2
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Redlock Redis-based distributed locks implementation in Ruby
98
+ test_files: []