hot_tub 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ module HotTub
2
+ class Reaper < Thread
3
+
4
+ # Creates a new Reaper thread for work.
5
+ # Expects an object that responses to: :reap!
6
+ # :shutdown and :reap_timeout
7
+ # Threads swallow exceptions until they are joined,
8
+ # so we rescue, log, and kill the reaper when an exception occurs
9
+ # https://bugs.ruby-lang.org/issues/6647
10
+ def self.spawn(obj)
11
+ th = new {
12
+ loop do
13
+ begin
14
+ obj.reap!
15
+ break if obj.shutdown
16
+ sleep(obj.reap_timeout || 600)
17
+ rescue Exception => e
18
+ HotTub.logger.error "HotTub::Reaper for #{obj.class.name} terminated with exception: #{e.message}"
19
+ HotTub.logger.error e.backtrace.map {|line| " #{line}"}
20
+ break
21
+ end
22
+ end
23
+ }
24
+ th[:name] = "HotTub::Reaper"
25
+ th
26
+ end
27
+
28
+ # Mixin to dry up Reaper usage
29
+ module Mixin
30
+ attr_reader :reap_timeout, :reaper, :shutdown
31
+
32
+ def reap!
33
+ raise NoMethodError.new(':reap! must be redefined in your class')
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,157 @@
1
+ require 'uri'
2
+ module HotTub
3
+ class Sessions
4
+ include HotTub::KnownClients
5
+ include HotTub::Reaper::Mixin
6
+
7
+ # HotTub::Session is a ThreadSafe::Cache where URLs are mapped to clients or pools.
8
+ #
9
+ #
10
+ # == Example with Pool:
11
+ # You can initialize a HotTub::Pool with each client by passing :with_pool as true and any pool options
12
+ # sessions = HotTub::Sessions.new(:with_pool => true, :size => 12) {
13
+ # uri = URI.parse("http://somewebservice.com")
14
+ # http = Net::HTTP.new(uri.host, uri.port)
15
+ # http.use_ssl = false
16
+ # http.start
17
+ # http
18
+ # }
19
+ #
20
+ # sessions.run("http://wwww.yahoo.com") do |conn|
21
+ # p conn.head('/').code
22
+ # end
23
+ #
24
+ # sessions.run("https://wwww.google.com") do |conn|
25
+ # p conn.head('/').code
26
+ # end
27
+ #
28
+ # == Excon clients are initialized to a specific domain. Its sometimes useful
29
+ # to have the options of initializing Excon connections after startup, in
30
+ # a thread safe manner for multiple urls with a single object.
31
+ # Example:
32
+ #
33
+ # sessions = HotTub::Sessions.new { |url| Excon.new(url) }
34
+ #
35
+ # sessions.run("http://wwww.yahoo.com") do |conn|
36
+ # p conn.head.status
37
+ # end
38
+ #
39
+ # sessions.run("https://wwww.google.com") do |conn|
40
+ # p conn.head.status
41
+ # end
42
+ #
43
+ #
44
+ # === OPTIONS
45
+ # [:with_pool]
46
+ # Default is false. A boolean that if true, wraps the new_client block in HotTub::Pool.new. All options
47
+ # are passed to the new HotTub::Pool object. See HotTub::Pool for options
48
+ # [:close]
49
+ # Default is nil. Can be a symbol representing an method to call on a client to close the client or a lambda
50
+ # that accepts the client as a parameter that will close a client. The close option is performed on clients
51
+ # on reaping and shutdown after the client has been removed from the pool. When nil, as is the default, no
52
+ # action is performed.
53
+ # [:clean]
54
+ # Default is nil. Can be a symbol representing an method to call on a client to clean the client or a lambda
55
+ # that accepts the client as a parameter that will clean a client. When nil, as is the default, no action is
56
+ # performed.
57
+ # [:reap]
58
+ # Default is nil. Can be a symbol representing an method to call on a client that returns a boolean marking
59
+ # a client for reaping, or a lambda that accepts the client as a parameter that returns a boolean boolean
60
+ # marking a client for reaping. When nil, as is the default, no action is performed.
61
+ # [:no_reaper]
62
+ # Default is nil. A boolean like value that if true prevents the reaper from initializing
63
+ #
64
+ def initialize(opts={},&new_client)
65
+ raise ArgumentError, "HotTub::Sessions require a block on initialization that accepts a single argument" unless block_given?
66
+ @with_pool = opts[:with_pool] # Set to true to use HotTub::Pool with supplied new_client block
67
+ @close_client = opts[:close] # => lambda {|clnt| clnt.close}
68
+ @clean_client = opts[:clean] # => lambda {|clnt| clnt.clean}
69
+ @reap_client = opts[:reap] # => lambda {|clnt| clnt.reap?} # should return boolean
70
+ @new_client = new_client # => { |url| MyClient.new(url) } # block that accepts a url param
71
+ @sessions = ThreadSafe::Cache.new
72
+ @shutdown = false
73
+ @reap_timeout = (opts[:reap_timeout] || 600) # the interval to reap connections in seconds
74
+ @reaper = Reaper.spawn(self) unless opts[:no_reaper]
75
+ @pool_options = {:no_reaper => true}.merge(opts) if @with_pool
76
+ at_exit {drain!}
77
+ end
78
+
79
+ # Safely initializes of sessions
80
+ # expects a url string or URI
81
+ def session(url)
82
+ key = to_key(url)
83
+ return @sessions.get(key) if @sessions.get(key)
84
+ if @with_pool
85
+ @sessions.compute_if_absent(key) {
86
+ HotTub::Pool.new(@pool_options) { @new_client.call(url) }
87
+ }
88
+ else
89
+ @sessions.compute_if_absent(key) {@new_client.call(url)}
90
+ end
91
+ @sessions.get(key)
92
+ end
93
+ alias :sessions :session
94
+
95
+ def run(url,&block)
96
+ session = sessions(url)
97
+ return session.run(&block) if session.is_a?(HotTub::Pool)
98
+ block.call(sessions(url))
99
+ end
100
+
101
+ def clean!
102
+ @sessions.each_pair do |key,clnt|
103
+ if clnt.is_a?(HotTub::Pool)
104
+ clnt.clean!
105
+ else
106
+ clean_client(clnt)
107
+ end
108
+ end
109
+ end
110
+
111
+ def drain!
112
+ @sessions.each_pair do |key,clnt|
113
+ if clnt.is_a?(HotTub::Pool)
114
+ clnt.drain!
115
+ else
116
+ close_client(clnt)
117
+ end
118
+ end
119
+ @sessions.clear
120
+ end
121
+
122
+ def shutdown!
123
+ @sessions.each_pair do |key,clnt|
124
+ if clnt.is_a?(HotTub::Pool)
125
+ clnt.shutdown!
126
+ else
127
+ close_client(clnt)
128
+ end
129
+ end
130
+ @sessions.clear
131
+ end
132
+
133
+ # Remove and close extra clients
134
+ def reap!
135
+ @sessions.each_pair do |key,clnt|
136
+ if clnt.is_a?(HotTub::Pool)
137
+ clnt.reap!
138
+ else
139
+ close_client(clnt) if reap_client?(clnt)
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def to_key(url)
147
+ if url.is_a?(String)
148
+ uri = URI(url)
149
+ elsif url.is_a?(URI)
150
+ uri = url
151
+ else
152
+ raise ArgumentError, "you must pass a string or a URI object"
153
+ end
154
+ "#{uri.scheme}://#{uri.host}:#{uri.port}"
155
+ end
156
+ end
157
+ end
@@ -1,3 +1,3 @@
1
1
  module HotTub
2
- VERSION = "0.2.6"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,13 +1,15 @@
1
1
  class MocClient
2
2
  def initialize(url=nil,options={})
3
+ @reaped = false
3
4
  @close = false
4
5
  @clean = false
5
6
  end
6
7
 
7
8
  # Perform an IO
8
9
  def get
9
- sleep(self.class.sleep_time)
10
- "that was slow IO"
10
+ prng = Random.new()
11
+ t_s = "0.0#{prng.rand(1..9)}".to_f
12
+ sleep(t_s)
11
13
  end
12
14
 
13
15
  def close
@@ -26,9 +28,11 @@ class MocClient
26
28
  @clean == true
27
29
  end
28
30
 
29
- class << self
30
- def sleep_time
31
- 0.2
32
- end
31
+ def reap
32
+ @reaped = true
33
+ end
34
+
35
+ def reaped?
36
+ @reaped
33
37
  end
34
38
  end
@@ -0,0 +1,23 @@
1
+ class MocMixinPool
2
+ include HotTub::Reaper::Mixin
3
+ end
4
+
5
+ class MocPool < MocMixinPool
6
+ attr_accessor :reaped, :lets_reap
7
+
8
+ def initialize
9
+ @reaped = false
10
+ @lets_reap = false
11
+ end
12
+
13
+ def reap!
14
+ @reaped = true if @lets_reap
15
+ end
16
+ end
17
+
18
+ class MocReaperPool < MocPool
19
+ def initialize
20
+ super
21
+ @reaper = HotTub::Reaper.spawn(self)
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ describe HotTub do
3
+
4
+ context "helpers" do
5
+ describe '#new' do
6
+
7
+ it "should return a HotTub::Pool" do
8
+ (HotTub.new { |url| MocClient.new(url) }).should be_a(HotTub::Pool)
9
+ end
10
+
11
+ context ':sessions => true' do
12
+ it "should be a HotTub::Sessions with HotTub::Pool as client" do
13
+ session_with_pool = HotTub.new(:sessions => true) { |url| MocClient.new(url) }
14
+ pool = session_with_pool.sessions("http://test.com")
15
+ pool.should be_a(HotTub::Pool)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/spec/pool_spec.rb CHANGED
@@ -1,8 +1,4 @@
1
1
  require 'spec_helper'
2
- unless HotTub.jruby?
3
- require "em-synchrony"
4
- require "em-synchrony/em-http"
5
- end
6
2
  describe HotTub::Pool do
7
3
 
8
4
  context 'default settings' do
@@ -11,37 +7,41 @@ describe HotTub::Pool do
11
7
  end
12
8
 
13
9
  it "should have :size of 5" do
14
- @pool.instance_variable_get(:@options)[:size].should eql(5)
10
+ @pool.instance_variable_get(:@size).should eql(5)
15
11
  end
16
12
 
17
- it "should have :blocking_timeout of 0.5" do
18
- @pool.instance_variable_get(:@options)[:blocking_timeout].should eql(10)
13
+ it "should have :wait_timeout of 0.5" do
14
+ @pool.instance_variable_get(:@wait_timeout).should eql(10)
19
15
  end
20
16
 
21
17
  it "should have set the client" do
22
- @pool.instance_variable_get(:@client_block).call.should be_a(MocClient)
18
+ @pool.instance_variable_get(:@new_client).call.should be_a(MocClient)
23
19
  end
24
20
 
25
21
  it "should be true" do
26
- @pool.instance_variable_get(:@options)[:never_block].should be_true
22
+ @pool.instance_variable_get(:@max_size).should eql(0)
23
+ end
24
+
25
+ it "should have a HotTub::Reaper" do
26
+ @pool.reaper.should be_a(HotTub::Reaper)
27
27
  end
28
28
  end
29
29
 
30
30
  context 'custom settings' do
31
31
  before(:each) do
32
- @pool = HotTub::Pool.new(:size => 10, :blocking_timeout => 1.0, :never_block => false) { MocClient.new }
32
+ @pool = HotTub::Pool.new(:size => 10, :wait_timeout => 1.0, :max_size => 20) { MocClient.new }
33
33
  end
34
34
 
35
35
  it "should have :size of 5" do
36
- @pool.instance_variable_get(:@options)[:size].should eql(10)
36
+ @pool.instance_variable_get(:@size).should eql(10)
37
37
  end
38
38
 
39
- it "should have :blocking_timeout of 0.5" do
40
- @pool.instance_variable_get(:@options)[:blocking_timeout].should eql(1.0)
39
+ it "should have :wait_timeout of 0.5" do
40
+ @pool.instance_variable_get(:@wait_timeout).should eql(1.0)
41
41
  end
42
42
 
43
43
  it "should be true" do
44
- @pool.instance_variable_get(:@options)[:never_block].should be_false
44
+ @pool.instance_variable_get(:@max_size).should eql(20)
45
45
  end
46
46
  end
47
47
 
@@ -58,11 +58,12 @@ describe HotTub::Pool do
58
58
  end
59
59
 
60
60
  it "should return the connection after use" do
61
+ @connection = nil
61
62
  @pool.run do |connection|
62
63
  @connection = connection
63
64
  end
64
65
  # returned to pool after work was done
65
- @pool.instance_variable_get(:@pool).select{|c| c.object_id == @connection.object_id}.length.should eql(1)
66
+ @pool.instance_variable_get(:@pool).pop.should eql(@connection)
66
67
  end
67
68
 
68
69
  it "should work" do
@@ -78,28 +79,60 @@ describe HotTub::Pool do
78
79
  end
79
80
  end
80
81
 
81
- describe '#close_all' do
82
+ describe '#drain!' do
82
83
  before(:each) do
83
84
  @pool = HotTub::Pool.new(:size => 5) { MocClient.new }
84
- 5.times do
85
- @pool.send(:_add)
85
+ @pool.instance_variable_set(:@out, [MocClient.new,MocClient.new,MocClient.new])
86
+ @pool.instance_variable_set(:@pool, [MocClient.new,MocClient.new,MocClient.new])
87
+ end
88
+
89
+ context ":close_out" do
90
+ it "should reset out" do
91
+ @pool.instance_variable_set(:@close_out, true)
92
+ @pool.drain!
93
+ @pool.instance_variable_get(:@out).length.should eql(0)
94
+ end
95
+ end
96
+
97
+ it "should reset pool" do
98
+ @pool.drain!
99
+ @pool.instance_variable_get(:@pool).length.should eql(0)
100
+ @pool.send(:_total_current_size).should eql(3)
101
+ @pool.instance_variable_get(:@out).length.should eql(3)
102
+ end
103
+ end
104
+
105
+ describe '#clean!' do
106
+ before(:each) do
107
+ @pool = HotTub::Pool.new(:size => 3, :clean => lambda { |clnt| clnt.clean}) { MocClient.new }
108
+ end
109
+
110
+ it "should clean pool" do
111
+ @pool.instance_variable_set(:@pool, [MocClient.new,MocClient.new,MocClient.new])
112
+ @pool.instance_variable_get(:@pool).first.cleaned?.should be_false
113
+ @pool.clean!
114
+ @pool.instance_variable_get(:@pool).each do |clnt|
115
+ clnt.cleaned?.should be_true
86
116
  end
87
117
  end
118
+ end
88
119
 
89
- it "should reset register" do
90
- @pool.current_size.should eql(5)
91
- @pool.instance_variable_get(:@register).length.should eql(5)
92
- @pool.close_all
93
- @pool.instance_variable_get(:@register).length.should eql(0)
94
- @pool.current_size.should eql(0)
120
+ describe '#shutdown!' do
121
+ before(:each) do
122
+ @pool = HotTub::Pool.new(:size => 5) { MocClient.new }
123
+ end
124
+
125
+ it "should kill reaper" do
126
+ @pool.shutdown!
127
+ sleep(0.01)
128
+ @pool.instance_variable_get(:@reaper).status.should be_false
95
129
  end
96
130
 
97
131
  it "should reset pool" do
98
- @pool.current_size.should eql(5)
99
- @pool.instance_variable_get(:@pool).length.should eql(5)
100
- @pool.close_all
132
+ @pool.instance_variable_set(:@pool, [MocClient.new,MocClient.new,MocClient.new])
133
+ @pool.shutdown!
101
134
  @pool.instance_variable_get(:@pool).length.should eql(0)
102
- @pool.current_size.should eql(0)
135
+ @pool.send(:_total_current_size).should eql(0)
103
136
  end
104
137
  end
105
138
 
@@ -111,9 +144,10 @@ describe HotTub::Pool do
111
144
 
112
145
  describe '#client' do
113
146
  it "should raise HotTub::BlockingTimeout if an available is not found in time"do
114
- @pool.instance_variable_set(:@options, {:never_block => false, :blocking_timeout => 0.1})
115
- @pool.stub(:pop).and_return(nil)
116
- lambda { puts @pool.send(:client) }.should raise_error(HotTub::BlockingTimeout)
147
+ @pool.instance_variable_set(:@non_blocking,false)
148
+ @pool.instance_variable_set(:@wait_timeout, 0.1)
149
+ @pool.stub(:raise_alarm?).and_return(true)
150
+ lambda { puts @pool.send(:pop) }.should raise_error(HotTub::Pool::Timeout)
117
151
  end
118
152
 
119
153
  it "should return an instance of the client" do
@@ -125,18 +159,18 @@ describe HotTub::Pool do
125
159
  it "should be true if @pool_data[:length] is less than desired pool size and
126
160
  the pool is empty?"do
127
161
  @pool.instance_variable_set(:@pool,[])
128
- @pool.send(:add?).should be_true
162
+ @pool.send(:_add?).should be_true
129
163
  end
130
164
 
131
165
  it "should be false pool has reached pool_size" do
132
- @pool.instance_variable_set(:@options,{:size => 5})
133
- @pool.instance_variable_set(:@pool,["connection","connection","connection","connection","connection"])
134
- @pool.send(:add?).should be_false
166
+ @pool.instance_variable_set(:@size, 5)
167
+ @pool.instance_variable_set(:@pool,[1,1,1,1,1])
168
+ @pool.send(:_add?).should be_false
135
169
  end
136
170
  end
137
171
 
138
172
  describe '#_add' do
139
- it "should add connections for supplied url"do
173
+ it "should add client for supplied url"do
140
174
  pre_add_length = @pool.instance_variable_get(:@pool).length
141
175
  @pool.send(:_add)
142
176
  @pool.instance_variable_get(:@pool).length.should be > pre_add_length
@@ -144,70 +178,66 @@ describe HotTub::Pool do
144
178
  end
145
179
 
146
180
  describe '#push' do
147
- context "connection is registered" do
148
- it "should push connection back to pool" do
181
+ context "client is registered" do
182
+ it "should push client back to pool" do
149
183
  @pool.send(:_add)
150
184
  clnt = @pool.instance_variable_get(:@pool).pop
151
185
  @pool.send(:push,clnt)
152
186
  @pool.instance_variable_get(:@pool).include?(clnt).should be_true
153
187
  end
154
188
  end
155
- context "connection is not registered" do
156
- it "should not push connection back to pool" do
157
- @pool.send(:_add)
158
- clnt = @pool.instance_variable_get(:@pool).pop
159
- @pool.instance_variable_get(:@register).delete(clnt)
160
- @pool.send(:push,clnt)
161
- @pool.instance_variable_get(:@pool).include?(clnt).should be_false
189
+ context "client is nil" do
190
+ it "should not push client back to pool" do
191
+ @pool.send(:push,nil)
192
+ @pool.instance_variable_get(:@pool).include?(nil).should be_false
162
193
  end
163
194
  end
164
195
  end
165
196
  end
166
197
 
167
- context ':never_block' do
168
- context 'is true' do
169
- it "should add connections to pool as necessary" do
198
+ context ':max_size' do
199
+ context 'is default' do
200
+ it "should add clients to pool as necessary" do
170
201
  pool = HotTub::Pool.new({:size => 1}) { MocClient.new }
171
202
  threads = []
172
203
  5.times.each do
173
204
  threads << Thread.new do
174
- pool.run{|connection| connection.get }
205
+ pool.run{|cltn| cltn.get }
175
206
  end
176
207
  end
177
- sleep(1)
178
208
  threads.each do |t|
179
209
  t.join
180
210
  end
181
- (pool.current_size > 1).should be_true
211
+ (pool.send(:_total_current_size) > 1).should be_true
182
212
  end
183
213
  end
184
- context 'is false' do
185
- it "should not add connections to pool beyond specified size" do
186
- pool = HotTub::Pool.new({:size => 1, :never_block => false, :blocking_timeout => 100}) { MocClient.new }
214
+ context 'is set' do
215
+ it "should not add clients to pool beyond specified size" do
216
+ pool = HotTub::Pool.new({:size => 1, :max_size => 1, :wait_timeout => 100}) { MocClient.new }
187
217
  threads = []
188
- 2.times.each do
218
+ 5.times.each do
189
219
  threads << Thread.new do
190
- pool.run{|connection| connection.get }
220
+ pool.run{|cltn| cltn.get }
191
221
  end
192
222
  end
193
- sleep(0.2)
194
223
  threads.each do |t|
195
224
  t.join
196
225
  end
197
- pool.current_size.should eql(1)
226
+ pool.send(:_total_current_size).should eql(1)
198
227
  end
199
228
  end
200
229
  end
201
230
 
202
- describe '#reap_pool' do
231
+ describe '#reap' do
203
232
  context 'current_size is greater than :size' do
204
- it "should remove a connection from the pool" do
233
+ it "should remove a client from the pool" do
205
234
  pool = HotTub::Pool.new({:size => 1}) { MocClient.new }
206
235
  pool.instance_variable_set(:@last_activity,(Time.now - 601))
207
- pool.instance_variable_set(:@pool, [MocClient.new,MocClient.new])
208
- pool.instance_variable_set(:@current_size,2)
209
- pool.send(:reap_pool)
210
- pool.current_size.should eql(1)
236
+ pool.instance_variable_set(:@pool, [MocClient.new,MocClient.new,MocClient.new])
237
+ pool.send(:_reap?).should be_true
238
+ pool.reaper.wakeup # run the reaper thread
239
+ sleep(0.1) # let results
240
+ pool.send(:_total_current_size).should eql(1)
211
241
  pool.instance_variable_get(:@pool).length.should eql(1)
212
242
  end
213
243
  end
@@ -224,7 +254,6 @@ describe HotTub::Pool do
224
254
  pool.run{|connection| connection.get }
225
255
  end
226
256
  end
227
- sleep(0.01)
228
257
  threads.each do |t|
229
258
  t.join
230
259
  end
@@ -245,131 +274,113 @@ describe HotTub::Pool do
245
274
  end
246
275
  end
247
276
 
248
- context 'Excon' do # Excon has its own pool, but just need to test with a real non-EM library
249
- before(:each) do
250
- @pool = HotTub::Pool.new(:size => 10) { Excon.new(HotTub::Server.url)}
251
- end
252
- it "should work" do
253
- result = nil
254
- @pool.run{|clnt| result = clnt.head.status}
255
- result.should eql(200)
256
- end
257
- context 'threads' do
277
+ context 'integration tests' do
278
+ context "blocking" do
279
+ before(:each) do
280
+ @pool = HotTub::Pool.new(:size => 5, :max_size => 5) {
281
+ uri = URI.parse(HotTub::Server.url)
282
+ http = Net::HTTP.new(uri.host, uri.port)
283
+ http.use_ssl = false
284
+ http.start
285
+ http
286
+ }
287
+ end
258
288
  it "should work" do
259
- failed = false
260
- threads = []
261
- lambda {
262
- 15.times.each do
263
- threads << Thread.new do
264
- @pool.run{|connection| Thread.current[:status] = connection.head.status }
265
- end
266
- end
267
- sleep(0.01)
268
- threads.each do |t|
269
- t.join
270
- end
271
- }.should_not raise_error
272
- # Reuse and run reaper
273
- @pool.instance_variable_set(:@last_activity,(Time.now - 601))
274
- lambda {
275
- 10.times.each do
276
- threads << Thread.new do
277
- @pool.run{|connection| Thread.current[:status] = connection.head.status }
278
- end
279
- end
280
- sleep(0.01)
281
- threads.each do |t|
282
- t.join
283
- end
284
- }.should_not raise_error
285
- results = threads.collect{ |t| t[:status]}
286
- results.length.should eql(25) # make sure all threads are present
287
- results.uniq.should eql([200]) # make sure all returned status 200
289
+ result = nil
290
+ @pool.run{|clnt|
291
+ uri = URI.parse(HotTub::Server.url)
292
+ result = clnt.head(uri.path).code
293
+ }
294
+ result.should eql('200')
288
295
  end
289
- end
290
- end
291
-
292
- unless HotTub.jruby?
293
- describe "em_client?" do
294
- context 'EM::HttpRequest as client' do
295
- before(:each) do
296
- @pool = HotTub::Pool.new { EM::HttpRequest.new(HotTub::Server.url) }
297
- end
298
- context "EM::Synchrony is present" do
299
- it "should be true" do
300
- HotTub.stub(:em_synchrony?).and_return(true)
301
- @pool.send(:em_client?).should be_true
302
- end
303
- end
304
- context "EM::Synchrony is not present" do
305
- it "should be false" do
306
- HotTub.stub(:em_synchrony?).and_return(false)
307
- @pool.send(:em_client?).should be_false
308
- end
309
- end
310
- end
311
- context 'client is not EM::HttpRequest' do
312
- it "should be false" do
313
- pool = HotTub::Pool.new {|url| MocClient.new}
314
- pool.send(:em_client?).should be_false
315
- end
296
+ context 'threads' do
297
+ it "should work" do
298
+ failed = false
299
+ threads = []
300
+ lambda { net_http_thread_work(@pool,10, threads) }.should_not raise_error
301
+ @pool.reap!
302
+ lambda { net_http_thread_work(@pool,20, threads) }.should_not raise_error
303
+ @pool.send(:_total_current_size).should eql(5) # make sure the pool grew beyond size
304
+ results = threads.collect{ |t| t[:status]}
305
+ results.length.should eql(30) # make sure all threads are present
306
+ results.uniq.should eql(['200']) # make sure all returned status 200
316
307
  end
317
308
  end
318
-
319
- context 'EM:HTTPRequest' do
309
+ end
310
+ context "never block without max" do
320
311
  before(:each) do
321
- @url = HotTub::Server.url
312
+ @pool = HotTub::Pool.new(:size => 5) {
313
+ uri = URI.parse(HotTub::Server.url)
314
+ http = Net::HTTP.new(uri.host, uri.port)
315
+ http.use_ssl = false
316
+ http.start
317
+ http
318
+ }
322
319
  end
323
-
324
320
  it "should work" do
325
- EM.synchrony do
326
- status = []
327
- c = HotTub::Pool.new {EM::HttpRequest.new(@url)}
328
- c.run { |conn| status << conn.head(:keepalive => true).response_header.status}
329
- c.run { |conn| status << conn.ahead(:keepalive => true).response_header.status}
330
- c.run { |conn| status << conn.head(:keepalive => true).response_header.status}
331
- status.should eql([200,0,200])
332
- c.close_all
333
- EM.stop
321
+ result = nil
322
+ @pool.run{|clnt|
323
+ uri = URI.parse(HotTub::Server.url)
324
+ result = clnt.head(uri.path).code
325
+ }
326
+ result.should eql('200')
327
+ end
328
+ context 'threads' do
329
+ it "should work" do
330
+ failed = false
331
+ threads = []
332
+ lambda { net_http_thread_work(@pool,10, threads) }.should_not raise_error
333
+ @pool.reap! # Force reaping to shrink pool back
334
+ lambda { net_http_thread_work(@pool,20, threads) }.should_not raise_error
335
+ @pool.send(:_total_current_size).should > 5 # make sure the pool grew beyond size
336
+ results = threads.collect{ |t| t[:status]}
337
+ results.length.should eql(30) # make sure all threads are present
338
+ results.uniq.should eql(['200']) # make sure all returned status 200
334
339
  end
335
340
  end
336
-
337
- context 'fibers' do
341
+ end
342
+ context "never block with max" do
343
+ before(:each) do
344
+ @pool = HotTub::Pool.new(:size => 5, :max_size => 10) {
345
+ uri = URI.parse(HotTub::Server.url)
346
+ http = Net::HTTP.new(uri.host, uri.port)
347
+ http.use_ssl = false
348
+ http.start
349
+ http
350
+ }
351
+ end
352
+ it "should work" do
353
+ result = nil
354
+ @pool.run{|clnt|
355
+ uri = URI.parse(HotTub::Server.url)
356
+ result = clnt.head(uri.path).code
357
+ }
358
+ result.should eql('200')
359
+ end
360
+ context 'threads' do
338
361
  it "should work" do
339
- EM.synchrony do
340
- pool = HotTub::Pool.new({:size => 5, :fiber_mutex => true}) {EM::HttpRequest.new(@url)}
341
- failed = false
342
- fibers = []
343
- lambda {
344
- 10.times.each do
345
- fibers << Fiber.new do
346
- pool.run{|connection|
347
- s = connection.head(:keepalive => true).response_header.status
348
- failed = true unless s == 200}
349
- end
350
- end
351
- fibers.each do |f|
352
- f.resume
353
- end
354
- loop do
355
- done = true
356
- fibers.each do |f|
357
- done = false if f.alive?
358
- end
359
- if done
360
- break
361
- else
362
- EM::Synchrony.sleep(0.01)
363
- end
364
- end
365
- }.should_not raise_error
366
- (pool.instance_variable_get(:@pool).length >= 5).should be_true #make sure work got done
367
- failed.should be_false # Make sure our requests worked
368
- pool.close_all
369
- EM.stop
370
- end
362
+ failed = false
363
+ threads = []
364
+ lambda { net_http_thread_work(@pool,10, threads) }.should_not raise_error
365
+ lambda { net_http_thread_work(@pool,20, threads) }.should_not raise_error
366
+ @pool.send(:_total_current_size).should > 5 # make sure pool is at max_size
367
+ results = threads.collect{ |t| t[:status]}
368
+ results.length.should eql(30) # make sure all threads are present
369
+ results.uniq.should eql(['200']) # make sure all returned status 200
371
370
  end
372
371
  end
373
372
  end
374
373
  end
374
+
375
+ def net_http_thread_work(pool,thread_count=0, threads=[])
376
+ thread_count.times.each do
377
+ threads << Thread.new do
378
+ uri = URI.parse(HotTub::Server.url)
379
+ pool.run{|connection| Thread.current[:status] = connection.head(uri.path).code }
380
+ end
381
+ end
382
+ threads.each do |t|
383
+ t.join
384
+ end
385
+ end
375
386
  end