suo 0.1.0 → 0.1.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 +4 -4
- data/.travis.yml +3 -0
- data/CHANGELOG.md +6 -1
- data/LICENSE.txt +22 -0
- data/README.md +1 -1
- data/Rakefile +2 -1
- data/lib/suo/client/base.rb +6 -5
- data/lib/suo/client/memcached.rb +16 -16
- data/lib/suo/client/redis.rb +16 -17
- data/lib/suo/clients.rb +2 -0
- data/lib/suo/version.rb +1 -1
- data/suo.gemspec +8 -5
- data/test/client_test.rb +154 -0
- data/test/test_helper.rb +9 -0
- metadata +27 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a30ddf4504c61a3c6e3896fea1300c8100140902
|
4
|
+
data.tar.gz: ec8b97cf374d959037c8825e9f0a6131f487dcff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e630516db130906d81fc7dafcb8c8532711710cfe6dbcd2932d2245a8294af716ac65b4d2551876acac7815a8d08593009489329c1f305f8f4927e64cb19b2e0
|
7
|
+
data.tar.gz: 135a570b6781da6ffb9232845a6ef7a9c096edb63025588b77173b6b93f69e63ceeb8de41a8818aa2d4cbf3c9d15b4d7ed2f04ff6bf2973c3cea47d74bcd77f7
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Nick Elser
|
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
CHANGED
data/Rakefile
CHANGED
data/lib/suo/client/base.rb
CHANGED
@@ -82,14 +82,15 @@ module Suo
|
|
82
82
|
private
|
83
83
|
|
84
84
|
def serialize_locks(locks)
|
85
|
-
locks.map { |time, token| [time.to_f, token]
|
85
|
+
MessagePack.pack(locks.map { |time, token| [time.to_f, token] })
|
86
86
|
end
|
87
87
|
|
88
|
-
def deserialize_locks(
|
89
|
-
|
90
|
-
time, token
|
91
|
-
[Time.at(time.to_f), token]
|
88
|
+
def deserialize_locks(val)
|
89
|
+
MessagePack.unpack(val).map do |time, token|
|
90
|
+
[Time.at(time), token]
|
92
91
|
end
|
92
|
+
rescue EOFError => _
|
93
|
+
[]
|
93
94
|
end
|
94
95
|
|
95
96
|
def clear_expired_locks(locks, options)
|
data/lib/suo/client/memcached.rb
CHANGED
@@ -16,7 +16,12 @@ module Suo
|
|
16
16
|
begin
|
17
17
|
start = Time.now.to_f
|
18
18
|
|
19
|
-
options[:retry_count].times do
|
19
|
+
options[:retry_count].times do
|
20
|
+
if options[:retry_timeout]
|
21
|
+
now = Time.now.to_f
|
22
|
+
break if now - start > options[:retry_timeout]
|
23
|
+
end
|
24
|
+
|
20
25
|
val, cas = client.get_cas(key)
|
21
26
|
|
22
27
|
# no key has been set yet; we could simply set it, but would lead to race conditions on the initial setting
|
@@ -38,11 +43,6 @@ module Suo
|
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
41
|
-
if options[:retry_timeout]
|
42
|
-
now = Time.now.to_f
|
43
|
-
break if now - start > options[:retry_timeout]
|
44
|
-
end
|
45
|
-
|
46
46
|
sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
|
47
47
|
end
|
48
48
|
rescue => _
|
@@ -60,6 +60,11 @@ module Suo
|
|
60
60
|
start = Time.now.to_f
|
61
61
|
|
62
62
|
options[:retry_count].times do
|
63
|
+
if options[:retry_timeout]
|
64
|
+
now = Time.now.to_f
|
65
|
+
break if now - start > options[:retry_timeout]
|
66
|
+
end
|
67
|
+
|
63
68
|
val, cas = client.get_cas(key)
|
64
69
|
|
65
70
|
# much like with initial set - ensure the key is here
|
@@ -76,11 +81,6 @@ module Suo
|
|
76
81
|
|
77
82
|
break if client.set_cas(key, newval, cas)
|
78
83
|
|
79
|
-
if options[:retry_timeout]
|
80
|
-
now = Time.now.to_f
|
81
|
-
break if now - start > options[:retry_timeout]
|
82
|
-
end
|
83
|
-
|
84
84
|
sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
|
85
85
|
end
|
86
86
|
rescue => _
|
@@ -98,6 +98,11 @@ module Suo
|
|
98
98
|
start = Time.now.to_f
|
99
99
|
|
100
100
|
options[:retry_count].times do
|
101
|
+
if options[:retry_timeout]
|
102
|
+
now = Time.now.to_f
|
103
|
+
break if now - start > options[:retry_timeout]
|
104
|
+
end
|
105
|
+
|
101
106
|
val, cas = client.get_cas(key)
|
102
107
|
|
103
108
|
break if val.nil? # lock has expired totally
|
@@ -114,11 +119,6 @@ module Suo
|
|
114
119
|
|
115
120
|
# another client cleared a token in the interim - try again!
|
116
121
|
|
117
|
-
if options[:retry_timeout]
|
118
|
-
now = Time.now.to_f
|
119
|
-
break if now - start > options[:retry_timeout]
|
120
|
-
end
|
121
|
-
|
122
122
|
sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
|
123
123
|
end
|
124
124
|
rescue => boom # rubocop:disable Lint/HandleExceptions
|
data/lib/suo/client/redis.rb
CHANGED
@@ -17,6 +17,11 @@ module Suo
|
|
17
17
|
start = Time.now.to_f
|
18
18
|
|
19
19
|
options[:retry_count].times do
|
20
|
+
if options[:retry_timeout]
|
21
|
+
now = Time.now.to_f
|
22
|
+
break if now - start > options[:retry_timeout]
|
23
|
+
end
|
24
|
+
|
20
25
|
client.watch(key) do
|
21
26
|
begin
|
22
27
|
val = client.get(key)
|
@@ -41,15 +46,9 @@ module Suo
|
|
41
46
|
|
42
47
|
break if acquisition_token
|
43
48
|
|
44
|
-
if options[:retry_timeout]
|
45
|
-
now = Time.now.to_f
|
46
|
-
break if now - start > options[:retry_timeout]
|
47
|
-
end
|
48
|
-
|
49
49
|
sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
|
50
50
|
end
|
51
|
-
rescue =>
|
52
|
-
raise boom
|
51
|
+
rescue => _
|
53
52
|
raise Suo::Client::FailedToAcquireLock
|
54
53
|
end
|
55
54
|
|
@@ -65,6 +64,11 @@ module Suo
|
|
65
64
|
start = Time.now.to_f
|
66
65
|
|
67
66
|
options[:retry_count].times do
|
67
|
+
if options[:retry_timeout]
|
68
|
+
now = Time.now.to_f
|
69
|
+
break if now - start > options[:retry_timeout]
|
70
|
+
end
|
71
|
+
|
68
72
|
client.watch(key) do
|
69
73
|
begin
|
70
74
|
val = client.get(key)
|
@@ -87,11 +91,6 @@ module Suo
|
|
87
91
|
|
88
92
|
break if refreshed
|
89
93
|
|
90
|
-
if options[:retry_timeout]
|
91
|
-
now = Time.now.to_f
|
92
|
-
break if now - start > options[:retry_timeout]
|
93
|
-
end
|
94
|
-
|
95
94
|
sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
|
96
95
|
end
|
97
96
|
rescue => _
|
@@ -111,6 +110,11 @@ module Suo
|
|
111
110
|
options[:retry_count].times do
|
112
111
|
cleared = false
|
113
112
|
|
113
|
+
if options[:retry_timeout]
|
114
|
+
now = Time.now.to_f
|
115
|
+
break if now - start > options[:retry_timeout]
|
116
|
+
end
|
117
|
+
|
114
118
|
client.watch(key) do
|
115
119
|
begin
|
116
120
|
val = client.get(key)
|
@@ -144,11 +148,6 @@ module Suo
|
|
144
148
|
|
145
149
|
break if cleared
|
146
150
|
|
147
|
-
if options[:retry_timeout]
|
148
|
-
now = Time.now.to_f
|
149
|
-
break if now - start > options[:retry_timeout]
|
150
|
-
end
|
151
|
-
|
152
151
|
sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
|
153
152
|
end
|
154
153
|
rescue => boom # rubocop:disable Lint/HandleExceptions
|
data/lib/suo/clients.rb
CHANGED
data/lib/suo/version.rb
CHANGED
data/suo.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "suo/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "suo"
|
@@ -10,12 +10,14 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["nick.elser@gmail.com"]
|
11
11
|
|
12
12
|
spec.summary = %q(Distributed semaphores using Memcached or Redis.)
|
13
|
-
|
13
|
+
spec.description = %q(Distributed semaphores using Memcached or Redis.)
|
14
14
|
spec.homepage = "https://github.com/nickelser/suo"
|
15
|
+
spec.license = "MIT"
|
15
16
|
|
16
|
-
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
18
|
spec.bindir = "bin"
|
18
|
-
spec.executables = spec.files.grep(%r{^
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
21
|
spec.require_paths = ["lib"]
|
20
22
|
|
21
23
|
spec.add_dependency "dalli"
|
@@ -25,4 +27,5 @@ Gem::Specification.new do |spec|
|
|
25
27
|
spec.add_development_dependency "bundler", "~> 1.5"
|
26
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
29
|
spec.add_development_dependency "rubocop", "~> 0.30.0"
|
30
|
+
spec.add_development_dependency "minitest", "~> 5.5.0"
|
28
31
|
end
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
TEST_KEY = "suo_test_key".freeze
|
4
|
+
|
5
|
+
module ClientTests
|
6
|
+
def test_requires_client
|
7
|
+
exception = assert_raises(RuntimeError) do
|
8
|
+
@klass.lock(TEST_KEY, 1)
|
9
|
+
end
|
10
|
+
|
11
|
+
assert_equal "Client required", exception.message
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_class_single_resource_locking
|
15
|
+
lock1 = @klass.lock(TEST_KEY, 1, client: @klass_client)
|
16
|
+
refute_nil lock1
|
17
|
+
|
18
|
+
locked = @klass.locked?(TEST_KEY, 1, client: @klass_client)
|
19
|
+
assert_equal true, locked
|
20
|
+
|
21
|
+
lock2 = @klass.lock(TEST_KEY, 1, client: @klass_client)
|
22
|
+
assert_nil lock2
|
23
|
+
|
24
|
+
@klass.unlock(TEST_KEY, lock1, client: @klass_client)
|
25
|
+
|
26
|
+
locked = @klass.locked?(TEST_KEY, 1, client: @klass_client)
|
27
|
+
assert_equal false, locked
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_class_multiple_resource_locking
|
31
|
+
lock1 = @klass.lock(TEST_KEY, 2, client: @klass_client)
|
32
|
+
refute_nil lock1
|
33
|
+
|
34
|
+
locked = @klass.locked?(TEST_KEY, 2, client: @klass_client)
|
35
|
+
assert_equal false, locked
|
36
|
+
|
37
|
+
lock2 = @klass.lock(TEST_KEY, 2, client: @klass_client)
|
38
|
+
refute_nil lock2
|
39
|
+
|
40
|
+
locked = @klass.locked?(TEST_KEY, 2, client: @klass_client)
|
41
|
+
assert_equal true, locked
|
42
|
+
|
43
|
+
@klass.unlock(TEST_KEY, lock1, client: @klass_client)
|
44
|
+
|
45
|
+
locked = @klass.locked?(TEST_KEY, 1, client: @klass_client)
|
46
|
+
assert_equal true, locked
|
47
|
+
|
48
|
+
@klass.unlock(TEST_KEY, lock2, client: @klass_client)
|
49
|
+
|
50
|
+
locked = @klass.locked?(TEST_KEY, 1, client: @klass_client)
|
51
|
+
assert_equal false, locked
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_instance_single_resource_locking
|
55
|
+
locked = false
|
56
|
+
|
57
|
+
@client.lock(TEST_KEY, 1) { locked = true }
|
58
|
+
|
59
|
+
assert_equal true, locked
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_instance_unlocks_on_exception
|
63
|
+
assert_raises(RuntimeError) do
|
64
|
+
@client.lock(TEST_KEY, 1) { fail "Test" }
|
65
|
+
end
|
66
|
+
|
67
|
+
locked = @klass.locked?(TEST_KEY, 1, client: @klass_client)
|
68
|
+
assert_equal false, locked
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_instance_multiple_resource_locking
|
72
|
+
success_counter = Queue.new
|
73
|
+
failure_counter = Queue.new
|
74
|
+
|
75
|
+
100.times.map do |i|
|
76
|
+
Thread.new do
|
77
|
+
success = @client.lock(TEST_KEY, 50, retry_timeout: 0.9) do
|
78
|
+
sleep(1)
|
79
|
+
success_counter << i
|
80
|
+
end
|
81
|
+
|
82
|
+
failure_counter << i unless success
|
83
|
+
end
|
84
|
+
end.map(&:join)
|
85
|
+
|
86
|
+
assert_equal 50, success_counter.size
|
87
|
+
assert_equal 50, failure_counter.size
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_instance_multiple_resource_locking_longer_timeout
|
91
|
+
success_counter = Queue.new
|
92
|
+
failure_counter = Queue.new
|
93
|
+
|
94
|
+
100.times.map do |i|
|
95
|
+
Thread.new do
|
96
|
+
success = @client.lock(TEST_KEY, 50, retry_timeout: 2) do
|
97
|
+
sleep(1)
|
98
|
+
success_counter << i
|
99
|
+
end
|
100
|
+
|
101
|
+
failure_counter << i unless success
|
102
|
+
end
|
103
|
+
end.map(&:join)
|
104
|
+
|
105
|
+
assert_equal 100, success_counter.size
|
106
|
+
assert_equal 0, failure_counter.size
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class TestBaseClient < Minitest::Test
|
111
|
+
def setup
|
112
|
+
@klass = Suo::Client::Base
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_not_implemented
|
116
|
+
assert_raises(NotImplementedError) do
|
117
|
+
@klass.lock(TEST_KEY, 1)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class TestMemcachedClient < Minitest::Test
|
123
|
+
include ClientTests
|
124
|
+
|
125
|
+
def setup
|
126
|
+
@klass = Suo::Client::Memcached
|
127
|
+
@client = @klass.new
|
128
|
+
@klass_client = Dalli::Client.new("127.0.0.1:11211")
|
129
|
+
end
|
130
|
+
|
131
|
+
def teardown
|
132
|
+
@klass_client.delete(TEST_KEY)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class TestRedisClient < Minitest::Test
|
137
|
+
include ClientTests
|
138
|
+
|
139
|
+
def setup
|
140
|
+
@klass = Suo::Client::Redis
|
141
|
+
@client = @klass.new
|
142
|
+
@klass_client = Redis.new
|
143
|
+
end
|
144
|
+
|
145
|
+
def teardown
|
146
|
+
@klass_client.del(TEST_KEY)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class TestLibrary < Minitest::Test
|
151
|
+
def test_that_it_has_a_version_number
|
152
|
+
refute_nil ::Suo::VERSION
|
153
|
+
end
|
154
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: suo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Elser
|
@@ -94,10 +94,26 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 0.30.0
|
97
|
-
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 5.5.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 5.5.0
|
111
|
+
description: Distributed semaphores using Memcached or Redis.
|
98
112
|
email:
|
99
113
|
- nick.elser@gmail.com
|
100
|
-
executables:
|
114
|
+
executables:
|
115
|
+
- console
|
116
|
+
- setup
|
101
117
|
extensions: []
|
102
118
|
extra_rdoc_files: []
|
103
119
|
files:
|
@@ -106,6 +122,7 @@ files:
|
|
106
122
|
- ".travis.yml"
|
107
123
|
- CHANGELOG.md
|
108
124
|
- Gemfile
|
125
|
+
- LICENSE.txt
|
109
126
|
- README.md
|
110
127
|
- Rakefile
|
111
128
|
- bin/console
|
@@ -118,8 +135,11 @@ files:
|
|
118
135
|
- lib/suo/clients.rb
|
119
136
|
- lib/suo/version.rb
|
120
137
|
- suo.gemspec
|
138
|
+
- test/client_test.rb
|
139
|
+
- test/test_helper.rb
|
121
140
|
homepage: https://github.com/nickelser/suo
|
122
|
-
licenses:
|
141
|
+
licenses:
|
142
|
+
- MIT
|
123
143
|
metadata: {}
|
124
144
|
post_install_message:
|
125
145
|
rdoc_options: []
|
@@ -141,4 +161,6 @@ rubygems_version: 2.4.5
|
|
141
161
|
signing_key:
|
142
162
|
specification_version: 4
|
143
163
|
summary: Distributed semaphores using Memcached or Redis.
|
144
|
-
test_files:
|
164
|
+
test_files:
|
165
|
+
- test/client_test.rb
|
166
|
+
- test/test_helper.rb
|