suo 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 200ad3e821080a0c0674af2abd4622de1c5088fb
4
+ data.tar.gz: 81a59e78a7396bd76b4a5d9c1e3cab46b0eac260
5
+ SHA512:
6
+ metadata.gz: f5ee6c325526dbe4ea6f4a68cd60a310c33b563a3b05495de4197093061634086581702ab25973df67410ef28345484a0ddf224a6d580ab863fe7ce732f777eb
7
+ data.tar.gz: 6f308c0243d9af16a65b33e8c35be667c27ce070500264ed236deb128c3bdfb4f9563aa879ece10d22386f4a92c4d56a42f2242003674a9038a585950f3d5839
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,216 @@
1
+ AllCops:
2
+ Exclude:
3
+ - .git/**/*
4
+ - tmp/**/*
5
+ - suo.gemspec
6
+
7
+ Lint/DuplicateMethods:
8
+ Enabled: true
9
+
10
+ Lint/DeprecatedClassMethods:
11
+ Enabled: true
12
+
13
+ Style/TrailingWhitespace:
14
+ Enabled: true
15
+
16
+ Style/Tab:
17
+ Enabled: true
18
+
19
+ Style/TrailingBlankLines:
20
+ Enabled: true
21
+
22
+ Style/NilComparison:
23
+ Enabled: true
24
+
25
+ Style/NonNilCheck:
26
+ Enabled: true
27
+
28
+ Style/Not:
29
+ Enabled: true
30
+
31
+ Style/RedundantReturn:
32
+ Enabled: true
33
+
34
+ Style/ClassCheck:
35
+ Enabled: true
36
+
37
+ Style/EmptyLines:
38
+ Enabled: true
39
+
40
+ Style/EmptyLiteral:
41
+ Enabled: true
42
+
43
+ Style/Alias:
44
+ Enabled: true
45
+
46
+ Style/MethodCallParentheses:
47
+ Enabled: true
48
+
49
+ Style/MethodDefParentheses:
50
+ Enabled: true
51
+
52
+ Style/SpaceBeforeBlockBraces:
53
+ Enabled: true
54
+
55
+ Style/SpaceInsideBlockBraces:
56
+ Enabled: true
57
+
58
+ Style/SpaceInsideParens:
59
+ Enabled: true
60
+
61
+ Style/DeprecatedHashMethods:
62
+ Enabled: true
63
+
64
+ Style/HashSyntax:
65
+ Enabled: true
66
+
67
+ Style/SpaceInsideHashLiteralBraces:
68
+ Enabled: true
69
+ EnforcedStyle: no_space
70
+
71
+ Style/SpaceInsideBrackets:
72
+ Enabled: true
73
+
74
+ Style/AndOr:
75
+ Enabled: false
76
+
77
+ Style/TrailingComma:
78
+ Enabled: true
79
+
80
+ Style/SpaceBeforeComma:
81
+ Enabled: true
82
+
83
+ Style/SpaceBeforeComment:
84
+ Enabled: true
85
+
86
+ Style/SpaceBeforeSemicolon:
87
+ Enabled: true
88
+
89
+ Style/SpaceAroundBlockParameters:
90
+ Enabled: true
91
+
92
+ Style/SpaceAroundOperators:
93
+ Enabled: true
94
+
95
+ Style/SpaceAfterColon:
96
+ Enabled: true
97
+
98
+ Style/SpaceAfterComma:
99
+ Enabled: true
100
+
101
+ Style/SpaceAfterControlKeyword:
102
+ Enabled: true
103
+
104
+ Style/SpaceAfterNot:
105
+ Enabled: true
106
+
107
+ Style/SpaceAfterSemicolon:
108
+ Enabled: true
109
+
110
+ Lint/UselessComparison:
111
+ Enabled: true
112
+
113
+ Lint/InvalidCharacterLiteral:
114
+ Enabled: true
115
+
116
+ Lint/LiteralInInterpolation:
117
+ Enabled: true
118
+
119
+ Lint/LiteralInCondition:
120
+ Enabled: true
121
+
122
+ Lint/UnusedBlockArgument:
123
+ Enabled: true
124
+
125
+ Style/VariableInterpolation:
126
+ Enabled: true
127
+
128
+ Style/RedundantSelf:
129
+ Enabled: true
130
+
131
+ Style/ParenthesesAroundCondition:
132
+ Enabled: true
133
+
134
+ Style/WhileUntilDo:
135
+ Enabled: true
136
+
137
+ Style/EmptyLineBetweenDefs:
138
+ Enabled: true
139
+
140
+ Style/EmptyLinesAroundAccessModifier:
141
+ Enabled: true
142
+
143
+ Style/EmptyLinesAroundMethodBody:
144
+ Enabled: true
145
+
146
+ Style/ColonMethodCall:
147
+ Enabled: true
148
+
149
+ Lint/SpaceBeforeFirstArg:
150
+ Enabled: true
151
+
152
+ Lint/UnreachableCode:
153
+ Enabled: true
154
+
155
+ Style/UnlessElse:
156
+ Enabled: true
157
+
158
+ Style/ClassVars:
159
+ Enabled: true
160
+
161
+ Style/StringLiterals:
162
+ Enabled: true
163
+ EnforcedStyle: double_quotes
164
+
165
+ Metrics/CyclomaticComplexity:
166
+ Max: 8
167
+
168
+ Metrics/LineLength:
169
+ Max: 128
170
+
171
+ Metrics/MethodLength:
172
+ Max: 32
173
+
174
+ Metrics/PerceivedComplexity:
175
+ Max: 8
176
+
177
+ # Disabled
178
+
179
+ Style/EvenOdd:
180
+ Enabled: false
181
+
182
+ Style/AsciiComments:
183
+ Enabled: false
184
+
185
+ Style/NumericLiterals:
186
+ Enabled: false
187
+
188
+ Style/UnneededPercentQ:
189
+ Enabled: false
190
+
191
+ Style/SpecialGlobalVars:
192
+ Enabled: false
193
+
194
+ Style/TrivialAccessors:
195
+ Enabled: false
196
+
197
+ Style/PerlBackrefs:
198
+ Enabled: false
199
+
200
+ Metrics/AbcSize:
201
+ Enabled: false
202
+
203
+ Metrics/BlockNesting:
204
+ Enabled: false
205
+
206
+ Metrics/ClassLength:
207
+ Enabled: false
208
+
209
+ Metrics/MethodLength:
210
+ Enabled: false
211
+
212
+ Metrics/ParameterLists:
213
+ Enabled: false
214
+
215
+ Metrics/PerceivedComplexity:
216
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Suo
2
+
3
+ :lock: Distributed semaphores using Memcached or Redis in Ruby.
4
+
5
+ Suo provides a very performant distributed lock solution using Compare-And-Set (`CAS`) commands in Memcached, and `WATCH/MULTI` in Redis.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application’s Gemfile:
10
+
11
+ ```ruby
12
+ gem 'suo'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Basic
18
+
19
+ ```ruby
20
+ # Memcached
21
+ suo = Suo::Client::Memcached.new(connection: "127.0.0.1:11211")
22
+
23
+ # Redis
24
+ suo = Suo::Client::Redis.new(connection: {host: "10.0.1.1"})
25
+
26
+ # Pre-existing client
27
+ suo = Suo::Client::Memcached.new(client: some_dalli_client)
28
+
29
+ suo.lock("some_key") do
30
+ # critical code here
31
+ @puppies.pet!
32
+ end
33
+
34
+ 2.times do
35
+ Thread.new do
36
+ # second argument is the number of resources - so this will run twice
37
+ suo.lock("other_key", 2, timeout: 0.5) { puts "Will run twice!" }
38
+ end
39
+ end
40
+ ```
41
+
42
+ ## TODO
43
+ - better stale key handling (refresh blocks)
44
+ - more race condition tests
45
+ - refactor clients to re-use more code
46
+
47
+ ## History
48
+
49
+ View the [changelog](https://github.com/nickelser/suo/blob/master/CHANGELOG.md)
50
+
51
+ ## Contributing
52
+
53
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
54
+
55
+ - [Report bugs](https://github.com/nickelser/suo/issues)
56
+ - Fix bugs and [submit pull requests](https://github.com/nickelser/suo/pulls)
57
+ - Write, clarify, or fix documentation
58
+ - Suggest or add new features
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.pattern = "test/*_test.rb"
7
+ end
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "suo"
5
+ require "irb"
6
+
7
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -0,0 +1,116 @@
1
+ module Suo
2
+ module Client
3
+ class Base
4
+ DEFAULT_OPTIONS = {
5
+ retry_count: 3,
6
+ retry_delay: 0.01,
7
+ stale_lock_expiration: 3600
8
+ }.freeze
9
+
10
+ def initialize(options = {})
11
+ @options = self.class.merge_defaults(options).merge(_initialized: true)
12
+ end
13
+
14
+ def lock(key, resources = 1, options = {})
15
+ options = self.class.merge_defaults(@options.merge(options))
16
+ token = self.class.lock(key, resources, options)
17
+
18
+ if token
19
+ begin
20
+ yield if block_given?
21
+ ensure
22
+ self.class.unlock(key, token, options)
23
+ end
24
+
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ def locked?(key, resources = 1)
32
+ self.class.locked?(key, resources, @options)
33
+ end
34
+
35
+ class << self
36
+ def lock(key, resources = 1, options = {}) # rubocop:disable Lint/UnusedMethodArgument
37
+ fail NotImplementedError
38
+ end
39
+
40
+ def locked?(key, resources = 1, options = {})
41
+ options = merge_defaults(options)
42
+ client = options[:client]
43
+ locks = deserialize_locks(client.get(key))
44
+
45
+ locks.size >= resources
46
+ end
47
+
48
+ def locks(key, options)
49
+ options = merge_defaults(options)
50
+ client = options[:client]
51
+ locks = deserialize_locks(client.get(key))
52
+
53
+ locks.size
54
+ end
55
+
56
+ def refresh(key, acquisition_token, options = {}) # rubocop:disable Lint/UnusedMethodArgument
57
+ fail NotImplementedError
58
+ end
59
+
60
+ def unlock(key, acquisition_token, options = {}) # rubocop:disable Lint/UnusedMethodArgument
61
+ fail NotImplementedError
62
+ end
63
+
64
+ def clear(key, options = {}) # rubocop:disable Lint/UnusedMethodArgument
65
+ fail NotImplementedError
66
+ end
67
+
68
+ def merge_defaults(options = {})
69
+ unless options[:_initialized]
70
+ options = self::DEFAULT_OPTIONS.merge(options)
71
+
72
+ fail "Client required" unless options[:client]
73
+ end
74
+
75
+ if options[:retry_timeout]
76
+ options[:retry_count] = (options[:retry_timeout] / options[:retry_delay].to_f).floor
77
+ end
78
+
79
+ options
80
+ end
81
+
82
+ private
83
+
84
+ def serialize_locks(locks)
85
+ locks.map { |time, token| [time.to_f, token].join(":") }.join(",")
86
+ end
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]
92
+ end
93
+ end
94
+
95
+ def clear_expired_locks(locks, options)
96
+ expired = Time.now - options[:stale_lock_expiration]
97
+ locks.reject { |time, _| time < expired }
98
+ end
99
+
100
+ def add_lock(locks, token)
101
+ locks << [Time.now.to_f, token]
102
+ end
103
+
104
+ def remove_lock(locks, acquisition_token)
105
+ lock = locks.find { |_, token| token == acquisition_token }
106
+ locks.delete(lock)
107
+ end
108
+
109
+ def refresh_lock(locks, acquisition_token)
110
+ remove_lock(locks, acquisition_token)
111
+ add_lock(locks, token)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,7 @@
1
+ module Suo
2
+ module Client
3
+ module Errors
4
+ class FailedToAcquireLock < StandardError; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,137 @@
1
+ module Suo
2
+ module Client
3
+ class Memcached < Base
4
+ def initialize(options = {})
5
+ options[:client] ||= Dalli::Client.new(options[:connection] || ENV["MEMCACHE_SERVERS"] || "127.0.0.1:11211")
6
+ super
7
+ end
8
+
9
+ class << self
10
+ def lock(key, resources = 1, options = {})
11
+ options = merge_defaults(options)
12
+ acquisition_token = nil
13
+ token = SecureRandom.base64(16)
14
+ client = options[:client]
15
+
16
+ begin
17
+ start = Time.now.to_f
18
+
19
+ options[:retry_count].times do |i|
20
+ val, cas = client.get_cas(key)
21
+
22
+ # no key has been set yet; we could simply set it, but would lead to race conditions on the initial setting
23
+ if val.nil?
24
+ client.set(key, "")
25
+ next
26
+ end
27
+
28
+ locks = clear_expired_locks(deserialize_locks(val.to_s), options)
29
+
30
+ if locks.size < resources
31
+ add_lock(locks, token)
32
+
33
+ newval = serialize_locks(locks)
34
+
35
+ if client.set_cas(key, newval, cas)
36
+ acquisition_token = token
37
+ break
38
+ end
39
+ end
40
+
41
+ if options[:retry_timeout]
42
+ now = Time.now.to_f
43
+ break if now - start > options[:retry_timeout]
44
+ end
45
+
46
+ sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
47
+ end
48
+ rescue => _
49
+ raise FailedToAcquireLock
50
+ end
51
+
52
+ acquisition_token
53
+ end
54
+
55
+ def refresh(key, acquisition_token, options = {})
56
+ options = merge_defaults(options)
57
+ client = options[:client]
58
+
59
+ begin
60
+ start = Time.now.to_f
61
+
62
+ options[:retry_count].times do
63
+ val, cas = client.get_cas(key)
64
+
65
+ # much like with initial set - ensure the key is here
66
+ if val.nil?
67
+ client.set(key, "")
68
+ next
69
+ end
70
+
71
+ locks = clear_expired_locks(deserialize_locks(val), options)
72
+
73
+ refresh_lock(locks, acquisition_token)
74
+
75
+ newval = serialize_locks(locks)
76
+
77
+ break if client.set_cas(key, newval, cas)
78
+
79
+ if options[:retry_timeout]
80
+ now = Time.now.to_f
81
+ break if now - start > options[:retry_timeout]
82
+ end
83
+
84
+ sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
85
+ end
86
+ rescue => _
87
+ raise FailedToAcquireLock
88
+ end
89
+ end
90
+
91
+ def unlock(key, acquisition_token, options = {})
92
+ options = merge_defaults(options)
93
+ client = options[:client]
94
+
95
+ return unless acquisition_token
96
+
97
+ begin
98
+ start = Time.now.to_f
99
+
100
+ options[:retry_count].times do
101
+ val, cas = client.get_cas(key)
102
+
103
+ break if val.nil? # lock has expired totally
104
+
105
+ locks = clear_expired_locks(deserialize_locks(val), options)
106
+
107
+ acquisition_lock = remove_lock(locks, acquisition_token)
108
+
109
+ break unless acquisition_lock
110
+
111
+ newval = serialize_locks(locks)
112
+
113
+ break if client.set_cas(key, newval, cas)
114
+
115
+ # another client cleared a token in the interim - try again!
116
+
117
+ if options[:retry_timeout]
118
+ now = Time.now.to_f
119
+ break if now - start > options[:retry_timeout]
120
+ end
121
+
122
+ sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
123
+ end
124
+ rescue => boom # rubocop:disable Lint/HandleExceptions
125
+ # since it's optimistic locking - fine if we are unable to release
126
+ raise boom if ENV["SUO_TEST"]
127
+ end
128
+ end
129
+
130
+ def clear(key, options = {})
131
+ options = merge_defaults(options)
132
+ options[:client].delete(key)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,167 @@
1
+ module Suo
2
+ module Client
3
+ class Redis < Base
4
+ def initialize(options = {})
5
+ options[:client] ||= ::Redis.new(options[:connection] || {})
6
+ super
7
+ end
8
+
9
+ class << self
10
+ def lock(key, resources = 1, options = {})
11
+ options = merge_defaults(options)
12
+ acquisition_token = nil
13
+ token = SecureRandom.base64(16)
14
+ client = options[:client]
15
+
16
+ begin
17
+ start = Time.now.to_f
18
+
19
+ options[:retry_count].times do
20
+ client.watch(key) do
21
+ begin
22
+ val = client.get(key)
23
+
24
+ locks = clear_expired_locks(deserialize_locks(val.to_s), options)
25
+
26
+ if locks.size < resources
27
+ add_lock(locks, token)
28
+
29
+ newval = serialize_locks(locks)
30
+
31
+ ret = client.multi do |multi|
32
+ multi.set(key, newval)
33
+ end
34
+
35
+ acquisition_token = token if ret[0] == "OK"
36
+ end
37
+ ensure
38
+ client.unwatch
39
+ end
40
+ end
41
+
42
+ break if acquisition_token
43
+
44
+ if options[:retry_timeout]
45
+ now = Time.now.to_f
46
+ break if now - start > options[:retry_timeout]
47
+ end
48
+
49
+ sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
50
+ end
51
+ rescue => boom
52
+ raise boom
53
+ raise Suo::Client::FailedToAcquireLock
54
+ end
55
+
56
+ acquisition_token
57
+ end
58
+
59
+ def refresh(key, acquisition_token, options = {})
60
+ options = merge_defaults(options)
61
+ client = options[:client]
62
+ refreshed = false
63
+
64
+ begin
65
+ start = Time.now.to_f
66
+
67
+ options[:retry_count].times do
68
+ client.watch(key) do
69
+ begin
70
+ val = client.get(key)
71
+
72
+ locks = clear_expired_locks(deserialize_locks(val), options)
73
+
74
+ refresh_lock(locks, acquisition_token)
75
+
76
+ newval = serialize_locks(locks)
77
+
78
+ ret = client.multi do |multi|
79
+ multi.set(key, newval)
80
+ end
81
+
82
+ refreshed = ret[0] == "OK"
83
+ ensure
84
+ client.unwatch
85
+ end
86
+ end
87
+
88
+ break if refreshed
89
+
90
+ if options[:retry_timeout]
91
+ now = Time.now.to_f
92
+ break if now - start > options[:retry_timeout]
93
+ end
94
+
95
+ sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
96
+ end
97
+ rescue => _
98
+ raise Suo::Client::FailedToAcquireLock
99
+ end
100
+ end
101
+
102
+ def unlock(key, acquisition_token, options = {})
103
+ options = merge_defaults(options)
104
+ client = options[:client]
105
+
106
+ return unless acquisition_token
107
+
108
+ begin
109
+ start = Time.now.to_f
110
+
111
+ options[:retry_count].times do
112
+ cleared = false
113
+
114
+ client.watch(key) do
115
+ begin
116
+ val = client.get(key)
117
+
118
+ if val.nil?
119
+ cleared = true
120
+ break
121
+ end
122
+
123
+ locks = clear_expired_locks(deserialize_locks(val), options)
124
+
125
+ acquisition_lock = remove_lock(locks, acquisition_token)
126
+
127
+ unless acquisition_lock
128
+ # token was already cleared
129
+ cleared = true
130
+ break
131
+ end
132
+
133
+ newval = serialize_locks(locks)
134
+
135
+ ret = client.multi do |multi|
136
+ multi.set(key, newval)
137
+ end
138
+
139
+ cleared = ret[0] == "OK"
140
+ ensure
141
+ client.unwatch
142
+ end
143
+ end
144
+
145
+ break if cleared
146
+
147
+ if options[:retry_timeout]
148
+ now = Time.now.to_f
149
+ break if now - start > options[:retry_timeout]
150
+ end
151
+
152
+ sleep(rand(options[:retry_delay] * 1000).to_f / 1000)
153
+ end
154
+ rescue => boom # rubocop:disable Lint/HandleExceptions
155
+ # since it's optimistic locking - fine if we are unable to release
156
+ raise boom if ENV["SUO_TEST"]
157
+ end
158
+ end
159
+
160
+ def clear(key, options = {})
161
+ options = merge_defaults(options)
162
+ options[:client].del(key)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,12 @@
1
+ require "securerandom"
2
+ require "monitor"
3
+
4
+ require "dalli"
5
+ require "dalli/cas/client"
6
+
7
+ require "redis"
8
+
9
+ require "suo/client/errors"
10
+ require "suo/client/base"
11
+ require "suo/client/memcached"
12
+ require "suo/client/redis"
@@ -0,0 +1,3 @@
1
+ module Suo
2
+ VERSION = "0.1.0"
3
+ end
data/lib/suo.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "suo/version"
2
+ require "suo/clients"
data/suo.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'suo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "suo"
8
+ spec.version = Suo::VERSION
9
+ spec.authors = ["Nick Elser"]
10
+ spec.email = ["nick.elser@gmail.com"]
11
+
12
+ spec.summary = %q(Distributed semaphores using Memcached or Redis.)
13
+ # spec.description = %q{TODO: Long description}
14
+ spec.homepage = "https://github.com/nickelser/suo"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "dalli"
22
+ spec.add_dependency "redis"
23
+ spec.add_dependency "msgpack"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.5"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rubocop", "~> 0.30.0"
28
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: suo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Elser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dalli
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: msgpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.30.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.30.0
97
+ description:
98
+ email:
99
+ - nick.elser@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rubocop.yml"
106
+ - ".travis.yml"
107
+ - CHANGELOG.md
108
+ - Gemfile
109
+ - README.md
110
+ - Rakefile
111
+ - bin/console
112
+ - bin/setup
113
+ - lib/suo.rb
114
+ - lib/suo/client/base.rb
115
+ - lib/suo/client/errors.rb
116
+ - lib/suo/client/memcached.rb
117
+ - lib/suo/client/redis.rb
118
+ - lib/suo/clients.rb
119
+ - lib/suo/version.rb
120
+ - suo.gemspec
121
+ homepage: https://github.com/nickelser/suo
122
+ licenses: []
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.4.5
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Distributed semaphores using Memcached or Redis.
144
+ test_files: []