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 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