innertube 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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