innertube 1.0.0 → 1.0.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.
- data/Gemfile +2 -0
- data/README.md +4 -4
- data/innertube.gemspec +4 -2
- data/lib/innertube.rb +3 -3
- data/lib/innertube/version.rb +1 -1
- data/spec/innertube_spec.rb +298 -0
- data/spec/spec_helper.rb +17 -0
- metadata +24 -3
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,18 +16,18 @@ licensed under the Apache 2.0 License.
|
|
16
16
|
pool = Innertube::Pool.new(proc { Connection.new },
|
17
17
|
proc {|c| c.disconnect })
|
18
18
|
|
19
|
-
# Grab a connection from the pool, returns the same value
|
19
|
+
# Grab a connection from the pool, returns the same value
|
20
20
|
# as the block
|
21
|
-
pool.take {|conn| conn.ping } # => true
|
21
|
+
pool.take {|conn| conn.ping } # => true
|
22
22
|
|
23
|
-
# Raise the BadResource exception if the resource is no
|
23
|
+
# Raise the BadResource exception if the resource is no
|
24
24
|
# longer good
|
25
25
|
pool.take do |conn|
|
26
26
|
raise Innertube::Pool::BadResource unless conn.connected?
|
27
27
|
conn.ping
|
28
28
|
end
|
29
29
|
|
30
|
-
# Innertube helps your code be re-entrant! Take more resources
|
30
|
+
# Innertube helps your code be re-entrant! Take more resources
|
31
31
|
# while you have one checked out.
|
32
32
|
pool.take do |conn|
|
33
33
|
conn.stream_tweets do |tweet|
|
data/innertube.gemspec
CHANGED
@@ -10,9 +10,11 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.homepage = "http://github.com/basho/innertube"
|
11
11
|
gem.authors = ["Sean Cribbs", "Kyle Kingsbury"]
|
12
12
|
|
13
|
-
|
13
|
+
gem.add_development_dependency 'rspec', '~> 2.10.0'
|
14
|
+
|
15
|
+
# Files
|
14
16
|
ignores = File.read(".gitignore").split(/\r?\n/).reject{ |f| f =~ /^(#.+|\s*)$/ }.map {|f| Dir[f] }.flatten
|
15
17
|
gem.files = (Dir['**/*','.gitignore'] - ignores).reject {|f| !File.file?(f) }
|
16
|
-
|
18
|
+
gem.test_files = (Dir['spec/**/*','.gitignore'] - ignores).reject {|f| !File.file?(f) }
|
17
19
|
gem.require_paths = ['lib']
|
18
20
|
end
|
data/lib/innertube.rb
CHANGED
@@ -28,7 +28,7 @@ module Innertube
|
|
28
28
|
# Claims this element of the pool for the current Thread.
|
29
29
|
# Do not call this manually, it is only used from inside the pool.
|
30
30
|
def lock
|
31
|
-
|
31
|
+
@owner = Thread.current
|
32
32
|
end
|
33
33
|
|
34
34
|
# @return [true,false] Is this element locked/claimed?
|
@@ -38,7 +38,7 @@ module Innertube
|
|
38
38
|
|
39
39
|
# Releases this element of the pool from the current Thread.
|
40
40
|
def unlock
|
41
|
-
|
41
|
+
@owner = nil
|
42
42
|
end
|
43
43
|
|
44
44
|
# @return [true,false] Is this element available for use?
|
@@ -114,7 +114,7 @@ module Innertube
|
|
114
114
|
element = nil
|
115
115
|
opts[:filter] ||= proc {|_| true }
|
116
116
|
@lock.synchronize do
|
117
|
-
element = pool.find { |e| e.unlocked? && opts[:filter].call(e.object) }
|
117
|
+
element = @pool.find { |e| e.unlocked? && opts[:filter].call(e.object) }
|
118
118
|
unless element
|
119
119
|
# No objects were acceptable
|
120
120
|
resource = opts[:default] || @open.call
|
data/lib/innertube/version.rb
CHANGED
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
describe Innertube::Pool do
|
5
|
+
let(:pool_members) { subject.instance_variable_get(:@pool) }
|
6
|
+
|
7
|
+
describe 'basics' do
|
8
|
+
subject do
|
9
|
+
described_class.new(lambda { [0] }, lambda { |x| })
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'yields a new object' do
|
13
|
+
subject.take do |x|
|
14
|
+
x.should == [0]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'retains a single object for serial access' do
|
19
|
+
n = 100
|
20
|
+
n.times do |i|
|
21
|
+
subject.take do |x|
|
22
|
+
x.should == [i]
|
23
|
+
x[0] += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
subject.size.should == 1
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should be re-entrant' do
|
30
|
+
n = 10
|
31
|
+
n.times do |i|
|
32
|
+
subject.take do |x|
|
33
|
+
x.replace [1]
|
34
|
+
subject.take do |y|
|
35
|
+
y.replace [2]
|
36
|
+
subject.take do |z|
|
37
|
+
z.replace [3]
|
38
|
+
subject.take do |t|
|
39
|
+
t.replace [4]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
subject.instance_variable_get(:@pool).map { |e| e.object.first }.sort.should == [1,2,3,4]
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
it 'should unlock when exceptions are raised' do
|
50
|
+
begin
|
51
|
+
subject.take do |x|
|
52
|
+
x << 1
|
53
|
+
subject.take do |y|
|
54
|
+
x << 2
|
55
|
+
y << 3
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
end
|
59
|
+
rescue
|
60
|
+
end
|
61
|
+
pool_members.should be_all {|e| not e.owner }
|
62
|
+
pool_members.map { |e| e.object }.should =~ [[0,1,2],[0,3]]
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when BadResource is raised' do
|
66
|
+
subject do
|
67
|
+
described_class.new(lambda do
|
68
|
+
m = mock('resource')
|
69
|
+
m.should_receive(:close)
|
70
|
+
m
|
71
|
+
end,
|
72
|
+
lambda do |res|
|
73
|
+
res.close
|
74
|
+
end)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should delete' do
|
78
|
+
lambda do
|
79
|
+
subject.take do |x|
|
80
|
+
raise Innertube::Pool::BadResource
|
81
|
+
end
|
82
|
+
end.should raise_error(Innertube::Pool::BadResource)
|
83
|
+
subject.size.should == 0
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'threads' do
|
89
|
+
subject {
|
90
|
+
described_class.new(lambda { [] }, lambda { |x| })
|
91
|
+
}
|
92
|
+
|
93
|
+
it 'should allocate n objects for n concurrent operations' do
|
94
|
+
# n threads concurrently allocate and sign objects from the pool
|
95
|
+
n = 10
|
96
|
+
readyq = Queue.new
|
97
|
+
finishq = Queue.new
|
98
|
+
threads = (0...n).map do
|
99
|
+
Thread.new do
|
100
|
+
subject.take do |x|
|
101
|
+
readyq << 1
|
102
|
+
x << Thread.current
|
103
|
+
finishq.pop
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
n.times { readyq.pop }
|
109
|
+
n.times { finishq << 1 }
|
110
|
+
|
111
|
+
# Wait for completion
|
112
|
+
threads.each do |t|
|
113
|
+
t.join
|
114
|
+
end
|
115
|
+
|
116
|
+
# Should have taken exactly n objects to do this
|
117
|
+
subject.size.should == n
|
118
|
+
# And each one should be signed exactly once
|
119
|
+
pool_members.map do |e|
|
120
|
+
e.object.size.should == 1
|
121
|
+
e.object.first
|
122
|
+
end.should =~ threads
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'take with filter and default' do
|
126
|
+
n = 10
|
127
|
+
subject = described_class.new(lambda { [] }, lambda { |x| })
|
128
|
+
|
129
|
+
# Allocate several elements of the pool
|
130
|
+
q = Queue.new
|
131
|
+
threads = (0...n).map do |i|
|
132
|
+
Thread.new do
|
133
|
+
subject.take do |a|
|
134
|
+
a << i
|
135
|
+
q << 1
|
136
|
+
sleep 0.02
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Wait for all threads to have acquired an element
|
142
|
+
n.times { q.pop }
|
143
|
+
|
144
|
+
threads.each do |t|
|
145
|
+
t.join
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get and delete existing even elements
|
149
|
+
got = Set.new
|
150
|
+
(n / 2).times do
|
151
|
+
begin
|
152
|
+
subject.take(
|
153
|
+
:filter => lambda { |x| x.first.even? },
|
154
|
+
:default => [:default]
|
155
|
+
) do |x|
|
156
|
+
got << x.first
|
157
|
+
raise Innertube::Pool::BadResource
|
158
|
+
end
|
159
|
+
rescue Innertube::Pool::BadResource
|
160
|
+
end
|
161
|
+
end
|
162
|
+
got.should == (0...n).select(&:even?).to_set
|
163
|
+
|
164
|
+
# This time, no even elements exist, so we should get the default.
|
165
|
+
subject.take(:filter => lambda { |x| x.first.even? },
|
166
|
+
:default => :default) do |x|
|
167
|
+
x.should == :default
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'iterates over a snapshot of all connections, even ones in use' do
|
172
|
+
started = Queue.new
|
173
|
+
n = 30
|
174
|
+
threads = (0..n).map do
|
175
|
+
Thread.new do
|
176
|
+
psleep = 0.75 * rand # up to 50ms sleep
|
177
|
+
subject.take do |a|
|
178
|
+
started << 1
|
179
|
+
a << rand
|
180
|
+
sleep psleep
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
n.times { started.pop }
|
186
|
+
touched = []
|
187
|
+
|
188
|
+
subject.each do |e|
|
189
|
+
touched << e
|
190
|
+
end
|
191
|
+
|
192
|
+
threads.each do |t|
|
193
|
+
t.join
|
194
|
+
end
|
195
|
+
|
196
|
+
touched.should be_all {|item| pool_members.find {|e| e.object == item } }
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should clear' do
|
200
|
+
n = 10
|
201
|
+
subject = described_class.new(
|
202
|
+
lambda { mock('connection').tap {|m| m.should_receive(:teardown) }},
|
203
|
+
lambda { |b| b.teardown }
|
204
|
+
)
|
205
|
+
|
206
|
+
# Allocate several elements of the pool
|
207
|
+
q = Queue.new
|
208
|
+
threads = (0...n).map do |i|
|
209
|
+
Thread.new do
|
210
|
+
subject.take do |a|
|
211
|
+
q << 1
|
212
|
+
sleep 0.1
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Wait for all threads to have acquired an element
|
218
|
+
n.times { q.pop }
|
219
|
+
|
220
|
+
# Clear the pool while threads still have elements checked out
|
221
|
+
subject.clear
|
222
|
+
pool_members.should be_empty
|
223
|
+
|
224
|
+
# Wait for threads to complete
|
225
|
+
threads.each do |t|
|
226
|
+
t.join
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should delete_if' do
|
231
|
+
n = 10
|
232
|
+
subject = described_class.new(
|
233
|
+
lambda { [] },
|
234
|
+
lambda { |x| }
|
235
|
+
)
|
236
|
+
|
237
|
+
# Allocate several elements of the pool
|
238
|
+
q = Queue.new
|
239
|
+
threads = (0...n).map do |i|
|
240
|
+
Thread.new do
|
241
|
+
subject.take do |a|
|
242
|
+
a << i
|
243
|
+
q << 1
|
244
|
+
sleep 0.02
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Wait for all threads to have acquired an element
|
250
|
+
n.times { q.pop }
|
251
|
+
|
252
|
+
# Delete odd elements
|
253
|
+
subject.delete_if do |x|
|
254
|
+
x.first.odd?
|
255
|
+
end
|
256
|
+
|
257
|
+
# Verify odds are gone.
|
258
|
+
pool_members.all? do |x|
|
259
|
+
x.object.first.even?
|
260
|
+
end.should == true
|
261
|
+
|
262
|
+
# Wait for threads
|
263
|
+
threads.each do |t|
|
264
|
+
t.join
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'stress test', :slow => true do
|
269
|
+
n = 100
|
270
|
+
psleep = 0.8
|
271
|
+
tsleep = 0.01
|
272
|
+
rounds = 100
|
273
|
+
|
274
|
+
threads = (0...n).map do
|
275
|
+
Thread.new do
|
276
|
+
rounds.times do |i|
|
277
|
+
subject.take do |a|
|
278
|
+
a.should == []
|
279
|
+
a << Thread.current
|
280
|
+
a.should == [Thread.current]
|
281
|
+
|
282
|
+
# Sleep and check
|
283
|
+
while rand < psleep
|
284
|
+
sleep tsleep
|
285
|
+
a.should == [Thread.current]
|
286
|
+
end
|
287
|
+
|
288
|
+
a.delete Thread.current
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
threads.each do |t|
|
294
|
+
t.join
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$: << File.expand_path('../../lib', __FILE__)
|
2
|
+
$: << File.expand_path('..', __FILE__)
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rspec'
|
5
|
+
require 'innertube'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.mock_with :rspec
|
9
|
+
config.filter_run :focus => true
|
10
|
+
config.run_all_when_everything_filtered = true
|
11
|
+
|
12
|
+
if defined?(::Java)
|
13
|
+
config.seed = Time.now.utc
|
14
|
+
else
|
15
|
+
config.order = :random
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: innertube
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,23 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
date: 2012-06-01 00:00:00.000000000 Z
|
14
|
-
dependencies:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.10.0
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 2.10.0
|
15
31
|
description: Because everyone needs their own pool library.
|
16
32
|
email:
|
17
33
|
- sean@basho.com
|
@@ -26,6 +42,8 @@ files:
|
|
26
42
|
- lib/innertube.rb
|
27
43
|
- LICENSE
|
28
44
|
- README.md
|
45
|
+
- spec/innertube_spec.rb
|
46
|
+
- spec/spec_helper.rb
|
29
47
|
- .gitignore
|
30
48
|
homepage: http://github.com/basho/innertube
|
31
49
|
licenses: []
|
@@ -51,4 +69,7 @@ rubygems_version: 1.8.23
|
|
51
69
|
signing_key:
|
52
70
|
specification_version: 3
|
53
71
|
summary: A thread-safe resource pool, originally borne in riak-client (Ripple).
|
54
|
-
test_files:
|
72
|
+
test_files:
|
73
|
+
- spec/innertube_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
- .gitignore
|