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 CHANGED
@@ -1 +1,3 @@
1
1
  source "http://rubygems.org"
2
+
3
+ gemspec
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|
@@ -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
- # Files
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
- # gem.test_files = (Dir['spec/**/*','.gitignore'] - ignores).reject {|f| !File.file?(f) }
18
+ gem.test_files = (Dir['spec/**/*','.gitignore'] - ignores).reject {|f| !File.file?(f) }
17
19
  gem.require_paths = ['lib']
18
20
  end
@@ -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
- self.owner = Thread.current
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
- self.owner = nil
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
@@ -1,3 +1,3 @@
1
1
  module Innertube
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -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
@@ -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.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