suo 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 200ad3e821080a0c0674af2abd4622de1c5088fb
4
- data.tar.gz: 81a59e78a7396bd76b4a5d9c1e3cab46b0eac260
3
+ metadata.gz: a30ddf4504c61a3c6e3896fea1300c8100140902
4
+ data.tar.gz: ec8b97cf374d959037c8825e9f0a6131f487dcff
5
5
  SHA512:
6
- metadata.gz: f5ee6c325526dbe4ea6f4a68cd60a310c33b563a3b05495de4197093061634086581702ab25973df67410ef28345484a0ddf224a6d580ab863fe7ce732f777eb
7
- data.tar.gz: 6f308c0243d9af16a65b33e8c35be667c27ce070500264ed236deb128c3bdfb4f9563aa879ece10d22386f4a92c4d56a42f2242003674a9038a585950f3d5839
6
+ metadata.gz: e630516db130906d81fc7dafcb8c8532711710cfe6dbcd2932d2245a8294af716ac65b4d2551876acac7815a8d08593009489329c1f305f8f4927e64cb19b2e0
7
+ data.tar.gz: 135a570b6781da6ffb9232845a6ef7a9c096edb63025588b77173b6b93f69e63ceeb8de41a8818aa2d4cbf3c9d15b4d7ed2f04ff6bf2973c3cea47d74bcd77f7
data/.travis.yml CHANGED
@@ -1,3 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.2.0
4
+ services:
5
+ - memcached
6
+ - redis-server
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.1.1
2
+
3
+ - Use [MessagePack](https://github.com/msgpack/msgpack-ruby) for semaphore serialization.
4
+
5
+
1
6
  ## 0.1.0
2
7
 
3
- - First release
8
+ - First release.
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
@@ -1,4 +1,4 @@
1
- # Suo
1
+ # Suo [![Build Status](https://travis-ci.org/nickelser/suo.png?branch=master)](https://travis-ci.org/nickelser/suo)
2
2
 
3
3
  :lock: Distributed semaphores using Memcached or Redis in Ruby.
4
4
 
data/Rakefile CHANGED
@@ -1,7 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
+ task default: :test
4
5
  Rake::TestTask.new do |t|
5
6
  t.libs << "test"
6
- t.pattern = "test/*_test.rb"
7
+ t.pattern = "test/**/*_test.rb"
7
8
  end
@@ -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].join(":") }.join(",")
85
+ MessagePack.pack(locks.map { |time, token| [time.to_f, token] })
86
86
  end
87
87
 
88
- def deserialize_locks(str)
89
- str.split(",").map do |s|
90
- time, token = s.split(":", 2)
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)
@@ -16,7 +16,12 @@ module Suo
16
16
  begin
17
17
  start = Time.now.to_f
18
18
 
19
- options[:retry_count].times do |i|
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
@@ -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 => boom
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
@@ -6,6 +6,8 @@ require "dalli/cas/client"
6
6
 
7
7
  require "redis"
8
8
 
9
+ require "msgpack"
10
+
9
11
  require "suo/client/errors"
10
12
  require "suo/client/base"
11
13
  require "suo/client/memcached"
data/lib/suo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Suo
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/suo.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'suo/version'
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
- # spec.description = %q{TODO: Long description}
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").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.bindir = "bin"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
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
@@ -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
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ require "suo"
4
+ require "thread"
5
+ require "minitest/autorun"
6
+ require "minitest/benchmark"
7
+
8
+ ENV["SUO_TEST"] = "true"
9
+
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.0
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
- description:
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