hot_tub 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/README.md +32 -14
- data/hot_tub.gemspec +0 -1
- data/lib/hot_tub/pool.rb +11 -16
- data/lib/hot_tub/session.rb +29 -26
- data/lib/hot_tub/version.rb +1 -1
- data/spec/pool_spec.rb +48 -29
- data/spec/session_spec.rb +108 -91
- data/spec/spec_helper.rb +13 -6
- metadata +4 -20
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# HotTub [![Build Status](https://travis-ci.org/JoshMcKin/hot_tub.png?branch=master)](https://travis-ci.org/JoshMcKin/hot_tub)
|
2
|
-
A simple thread-safe connection pooling gem.
|
2
|
+
A simple thread-safe connection pooling gem. Out-of-the-box support for [Excon](https://github.com/geemus/excon) and
|
3
3
|
[EM-Http-Requests](https://github.com/igrigorik/em-http-request) via [EM-Synchrony](https://github.com/igrigorik/em-synchrony).
|
4
4
|
There are a couple Ruby connection pool libraries out there but HotTub differs from most in that its connections are lazy
|
5
5
|
(created only when necessary), accomidates libraries that do not clean their dirty connections automatically, and manages unexpected usage increases by opening new connections rather than just blocking or throwing exceptions (never_block), although never_block can be disabled.
|
@@ -12,14 +12,15 @@ HotTub is available through [Rubygems](https://rubygems.org/gems/hot_tub) and ca
|
|
12
12
|
|
13
13
|
## Usage
|
14
14
|
|
15
|
-
###
|
15
|
+
### Excon
|
16
16
|
|
17
|
+
require 'excon'
|
17
18
|
class MyClass
|
18
19
|
@@url = "http://test12345.com"
|
19
|
-
@@pool = HotTub::Pool.new({:size => 10})
|
20
|
+
@@pool = HotTub::Pool.new({:size => 10}) { Excon.new("http://somewebservice.com") }
|
20
21
|
def self.fetch_results(url,query={})
|
21
22
|
@@pool.run |connection|
|
22
|
-
connection.get(
|
23
|
+
connection.get(:query => query).body
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -47,6 +48,7 @@ HotTub is available through [Rubygems](https://rubygems.org/gems/hot_tub) and ca
|
|
47
48
|
You can use any libary you want. Close and clean can be defined at initialization with
|
48
49
|
lambdas, if they are not defined they are ignored.
|
49
50
|
|
51
|
+
require 'excon'
|
50
52
|
class MyClass
|
51
53
|
@@url = "http://test12345.com"
|
52
54
|
@@pool = HotTub::Pool.new({:size => 10, :close => lambda {|clnt| clnt.close}}) { MyHttpLib.new }
|
@@ -59,18 +61,33 @@ HotTub is available through [Rubygems](https://rubygems.org/gems/hot_tub) and ca
|
|
59
61
|
|
60
62
|
MyClass.fetch_results({:foo => "goo"}) # => "Some reponse"
|
61
63
|
|
62
|
-
## Sessions
|
63
|
-
|
64
|
-
HTTPClient has a built in thread-safe sessions feature that allows a single client to access multiple domains.
|
65
|
-
Not all clients have a sessions feature, Em-Http-Request clients are initialized to a single domain and while you
|
64
|
+
## Sessions with Pool
|
65
|
+
Not all clients have a sessions feature, Excon and Em-Http-Request clients are initialized to a single domain and while you
|
66
66
|
can change paths the client domain cannot change. HotTub::Session allows you create a session object that initializes
|
67
|
-
seperate pools for
|
67
|
+
seperate pools for your various domains based on URI. Options are passed to the pool when each pool is initialized.
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
require 'excon'
|
70
|
+
class MyClass
|
71
|
+
# Our client block must accept the url argument
|
72
|
+
@@sessons = HotTub::Sessions.new(:size => 10) {|url| { Excon.new(url) }
|
73
|
+
def async_post_results(query = {})
|
74
|
+
@@sessons.run("http://somewebservice.com") do |connection|
|
75
|
+
puts connection.run(:query => results).response_header.status
|
76
|
+
end
|
77
|
+
@@sessons.run("https://someotherwebservice.com") do |connection|
|
78
|
+
puts connection.get(:query => results).response_header.status
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
## Sessions without Pool
|
84
|
+
If you have a client that is thread safe but does not support sessions you can implement sessions similarly. Excon
|
85
|
+
is thread safe but you are using a single client, so you may loose preformance with multithreading.
|
86
|
+
|
87
|
+
require 'excon'
|
88
|
+
class MyClass
|
72
89
|
# Our client block must accept the url argument
|
73
|
-
@@sessons = HotTub::Sessions.new {|url|
|
90
|
+
@@sessons = HotTub::Sessions.new(:with_pool => false) {|url| Excon.new(url) }
|
74
91
|
def async_post_results(query = {})
|
75
92
|
@@sessons.run("http://somewebservice.com") do |connection|
|
76
93
|
puts connection.get(:query => results).response_header.status
|
@@ -83,9 +100,10 @@ seperate pools for you various domains based on URI.
|
|
83
100
|
|
84
101
|
## Related
|
85
102
|
|
86
|
-
* [HTTPClient](https://github.com/nahi/httpclient)
|
87
103
|
* [EM-Http-Request](https://github.com/igrigorik/em-http-request)
|
88
104
|
* [EM-Synchrony](https://github.com/igrigorik/em-synchrony)
|
105
|
+
* [Excon](https://github.com/geemus/excon)
|
106
|
+
* [HTTPClient](https://github.com/nahi/httpclient) A thread safe http client that supports sessions all by itself.
|
89
107
|
|
90
108
|
## Other Pooling Gem
|
91
109
|
|
data/hot_tub.gemspec
CHANGED
@@ -13,7 +13,6 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{A simple thread-safe http connection pooling gem. Http client options include HTTPClient and EM-Http-Request}
|
14
14
|
|
15
15
|
s.rubyforge_project = "hot_tub"
|
16
|
-
s.add_runtime_dependency "httpclient"
|
17
16
|
s.add_development_dependency "rspec"
|
18
17
|
|
19
18
|
s.files = `git ls-files`.split("\n")
|
data/lib/hot_tub/pool.rb
CHANGED
@@ -1,13 +1,9 @@
|
|
1
|
-
require 'httpclient'
|
2
1
|
module HotTub
|
3
2
|
class Pool
|
4
3
|
attr_reader :current_size
|
5
4
|
KNOWN_CLIENTS = {
|
6
|
-
"
|
7
|
-
:close => lambda { |clnt|
|
8
|
-
sessions = clnt.instance_variable_get(:@session_manager)
|
9
|
-
sessions.reset_all if sessions
|
10
|
-
}
|
5
|
+
"Excon::Connection" => {
|
6
|
+
:close => lambda { |clnt| clnt.reset }
|
11
7
|
},
|
12
8
|
'EventMachine::HttpConnection' => {
|
13
9
|
:close => lambda { |clnt|
|
@@ -27,15 +23,13 @@ module HotTub
|
|
27
23
|
}
|
28
24
|
}
|
29
25
|
|
30
|
-
#
|
31
|
-
# The default client is HTTPClient.
|
32
|
-
# Clients must respond to :clean, :close, and :run
|
26
|
+
# Thread-safe lazy connection pool
|
33
27
|
#
|
34
|
-
# == Example
|
35
|
-
# pool = HotTub::Pool.new(:size => 25)
|
36
|
-
# pool.run {|clnt| clnt.get
|
28
|
+
# == Example Excon
|
29
|
+
# pool = HotTub::Pool.new(:size => 25) { Excon.new('http://test.com') }
|
30
|
+
# pool.run {|clnt| clnt.get.body }
|
37
31
|
#
|
38
|
-
# == Example
|
32
|
+
# == Example EM-Http-Request
|
39
33
|
# pool = HotTub::Pool.new { EM::HttpRequest.new("http://somewebservice.com") }
|
40
34
|
# pool.run {|clnt| clnt.get(:keepalive => true).body }
|
41
35
|
#
|
@@ -46,17 +40,18 @@ module HotTub
|
|
46
40
|
# add new connections set :never_block to false; blocking_timeout defaults to 10 seconds.
|
47
41
|
#
|
48
42
|
# == Example without #never_block (will BlockingTimeout exception)
|
49
|
-
# pool = HotTub::Pool.new(:size => 1, :never_block => false, :blocking_timeout => 0.5)
|
43
|
+
# pool = HotTub::Pool.new(:size => 1, :never_block => false, :blocking_timeout => 0.5) { Excon.new('http://test.com') }
|
50
44
|
#
|
51
45
|
# begin
|
52
|
-
# pool.run {|clnt| clnt.get
|
46
|
+
# pool.run {|clnt| clnt.get.body }
|
53
47
|
# rescue HotTub::BlockingTimeout => e
|
54
48
|
# puts "Our pool ran out: {e}"
|
55
49
|
# end
|
56
50
|
#
|
57
51
|
def initialize(options={},&client_block)
|
52
|
+
raise ArgumentError, 'a block that initializes a new client is required' unless block_given?
|
58
53
|
at_exit { close_all } # close connections at exit
|
59
|
-
@client_block =
|
54
|
+
@client_block = client_block
|
60
55
|
@options = {
|
61
56
|
:size => 5,
|
62
57
|
:never_block => true, # Return new client if we run out
|
data/lib/hot_tub/session.rb
CHANGED
@@ -2,15 +2,14 @@ require 'uri'
|
|
2
2
|
module HotTub
|
3
3
|
class Session
|
4
4
|
|
5
|
-
# A HotTub::Session is a synchronized hash used to separate
|
6
|
-
# EmHttpRequest clients are initialized to a specific domain, so we sometimes need a way to
|
7
|
-
# manage multiple pools like when a process need to connect to various AWS resources.
|
8
|
-
# are unnecessary for HTTPClient because the client has its own threads safe sessions object.
|
5
|
+
# A HotTub::Session is a synchronized hash used to separate pools/clients by their domain.
|
6
|
+
# Excon and EmHttpRequest clients are initialized to a specific domain, so we sometimes need a way to
|
7
|
+
# manage multiple pools like when a process need to connect to various AWS resources.
|
9
8
|
# Example:
|
10
9
|
#
|
11
|
-
# sessions = HotTub::Session.new(:client_options => {:connect_timeout => 10})
|
10
|
+
# sessions = HotTub::Session.new(:client_options => {:connect_timeout => 10}) { |url| Excon.new(url) }
|
12
11
|
#
|
13
|
-
# sessions.run("
|
12
|
+
# sessions.run("http://wwww.yahoo.com") do |conn|
|
14
13
|
# p conn.head.response_header.status
|
15
14
|
# end
|
16
15
|
#
|
@@ -23,7 +22,7 @@ module HotTub
|
|
23
22
|
# EmHttpRequest, accepting a URI and options see: hot_tub/clients/em_http_request_client.rb
|
24
23
|
# Example Custom Client:
|
25
24
|
#
|
26
|
-
# sessions = HotTub::Session.new(:
|
25
|
+
# sessions = HotTub::Session.new({:never_block => false}) { |url| Excon.new(url) }
|
27
26
|
#
|
28
27
|
# sessions.run("https://wwww.yahoo.com") do |conn|
|
29
28
|
# p conn.head.response_header.status # => create pool for "https://wwww.yahoo.com"
|
@@ -34,21 +33,35 @@ module HotTub
|
|
34
33
|
# end
|
35
34
|
def initialize(options={},&client_block)
|
36
35
|
raise ArgumentError, "HotTub::Sessions requre a block on initialization that accepts a single argument" unless block_given?
|
36
|
+
@options = {:with_pool => true}.merge(options)
|
37
37
|
@client_block = client_block
|
38
|
-
@options = {
|
39
|
-
:size => 5,
|
40
|
-
:never_block => true,
|
41
|
-
:blocking_timeout => 10,
|
42
|
-
:client_class => nil, # EmHttpRequestClient
|
43
|
-
:client_options => {}
|
44
|
-
}.merge(options || {})
|
45
38
|
@sessions = Hash.new
|
46
39
|
@mutex = (HotTub.em? ? EM::Synchrony::Thread::Mutex.new : Mutex.new)
|
47
40
|
end
|
48
41
|
|
49
|
-
#
|
42
|
+
# Synchronizes initialization of our sessions
|
50
43
|
# expects a url string or URI
|
51
44
|
def sessions(url)
|
45
|
+
key = to_key(url)
|
46
|
+
return @sessions[key] if @sessions[key]
|
47
|
+
@mutex.synchronize do
|
48
|
+
if @options[:with_pool]
|
49
|
+
@sessions[key] ||= HotTub::Pool.new(@options) { @client_block.call(url) }
|
50
|
+
else
|
51
|
+
@sessions[key] ||= @client_block.call(url)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def run(url,&block)
|
57
|
+
session = sessions(url)
|
58
|
+
return session.run(&block) if @options[:with_pool]
|
59
|
+
block.call(sessions(url))
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def to_key(url)
|
52
65
|
if url.is_a?(String)
|
53
66
|
uri = URI(url)
|
54
67
|
elsif url.is_a?(URI)
|
@@ -56,17 +69,7 @@ module HotTub
|
|
56
69
|
else
|
57
70
|
raise ArgumentError, "you must pass a string or a URI object"
|
58
71
|
end
|
59
|
-
|
60
|
-
return @sessions[key] if @sessions[key]
|
61
|
-
@mutex.synchronize do
|
62
|
-
@sessions[key] ||= HotTub::Pool.new(@options) { @client_block.call(url) }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Hand off to pool.run
|
67
|
-
def run(url,&block)
|
68
|
-
pool = sessions(url)
|
69
|
-
pool.run(&block) if pool
|
72
|
+
"#{uri.scheme}-#{uri.host}"
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
data/lib/hot_tub/version.rb
CHANGED
data/spec/pool_spec.rb
CHANGED
@@ -7,7 +7,7 @@ describe HotTub::Pool do
|
|
7
7
|
|
8
8
|
context 'default settings' do
|
9
9
|
before(:each) do
|
10
|
-
@pool = HotTub::Pool.new
|
10
|
+
@pool = HotTub::Pool.new { MocClient.new }
|
11
11
|
end
|
12
12
|
|
13
13
|
it "should have :size of 5" do
|
@@ -18,8 +18,8 @@ describe HotTub::Pool do
|
|
18
18
|
@pool.instance_variable_get(:@options)[:blocking_timeout].should eql(10)
|
19
19
|
end
|
20
20
|
|
21
|
-
it "should have
|
22
|
-
@pool.instance_variable_get(:@client_block).call.should be_a(
|
21
|
+
it "should have set the client" do
|
22
|
+
@pool.instance_variable_get(:@client_block).call.should be_a(MocClient)
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should be true" do
|
@@ -40,10 +40,6 @@ describe HotTub::Pool do
|
|
40
40
|
@pool.instance_variable_get(:@options)[:blocking_timeout].should eql(1.0)
|
41
41
|
end
|
42
42
|
|
43
|
-
it "should have defult :client" do
|
44
|
-
@pool.instance_variable_get(:@client_block).call.should be_a(MocClient)
|
45
|
-
end
|
46
|
-
|
47
43
|
it "should be true" do
|
48
44
|
@pool.instance_variable_get(:@options)[:never_block].should be_false
|
49
45
|
end
|
@@ -51,7 +47,7 @@ describe HotTub::Pool do
|
|
51
47
|
|
52
48
|
describe '#run' do
|
53
49
|
before(:each) do
|
54
|
-
@pool = HotTub::Pool.new
|
50
|
+
@pool = HotTub::Pool.new { MocClient.new}
|
55
51
|
end
|
56
52
|
|
57
53
|
it "should remove connection from pool when doing work" do
|
@@ -70,17 +66,9 @@ describe HotTub::Pool do
|
|
70
66
|
end
|
71
67
|
|
72
68
|
it "should work" do
|
73
|
-
|
74
|
-
@pool.run{|clnt|
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
context "block given" do
|
79
|
-
it "should call the supplied block" do
|
80
|
-
status = nil
|
81
|
-
@pool.run { |con| status = con.get('https://google.com').status}
|
82
|
-
status.should_not be_nil
|
83
|
-
end
|
69
|
+
result = nil
|
70
|
+
@pool.run{|clnt| result = clnt.get}
|
71
|
+
result.should_not be_nil
|
84
72
|
end
|
85
73
|
|
86
74
|
context 'block not given' do
|
@@ -92,7 +80,7 @@ describe HotTub::Pool do
|
|
92
80
|
|
93
81
|
describe '#close_all' do
|
94
82
|
before(:each) do
|
95
|
-
@pool = HotTub::Pool.new(:size => 5)
|
83
|
+
@pool = HotTub::Pool.new(:size => 5) { MocClient.new }
|
96
84
|
5.times do
|
97
85
|
@pool.send(:add)
|
98
86
|
end
|
@@ -111,7 +99,7 @@ describe HotTub::Pool do
|
|
111
99
|
context 'private methods' do
|
112
100
|
before(:each) do
|
113
101
|
@url = "http://www.testurl123.com/"
|
114
|
-
@pool = HotTub::Pool.new
|
102
|
+
@pool = HotTub::Pool.new { MocClient.new}
|
115
103
|
end
|
116
104
|
|
117
105
|
describe '#client' do
|
@@ -121,8 +109,8 @@ describe HotTub::Pool do
|
|
121
109
|
lambda { puts @pool.send(:client) }.should raise_error(HotTub::BlockingTimeout)
|
122
110
|
end
|
123
111
|
|
124
|
-
it "should return an instance of the
|
125
|
-
@pool.send(:client).should be_instance_of(
|
112
|
+
it "should return an instance of the client" do
|
113
|
+
@pool.send(:client).should be_instance_of(MocClient)
|
126
114
|
end
|
127
115
|
end
|
128
116
|
|
@@ -151,13 +139,13 @@ describe HotTub::Pool do
|
|
151
139
|
context 'thread safety' do
|
152
140
|
it "should work" do
|
153
141
|
url = "https://www.google.com/"
|
154
|
-
pool = HotTub::Pool.new({:size =>
|
142
|
+
pool = HotTub::Pool.new({:size => 10}) { MocClient.new }
|
155
143
|
failed = false
|
156
144
|
lambda {
|
157
145
|
threads = []
|
158
146
|
20.times.each do
|
159
147
|
threads << Thread.new do
|
160
|
-
pool.run{|connection|
|
148
|
+
pool.run{|connection| connection.get }
|
161
149
|
end
|
162
150
|
end
|
163
151
|
sleep(0.01)
|
@@ -165,15 +153,13 @@ describe HotTub::Pool do
|
|
165
153
|
t.join
|
166
154
|
end
|
167
155
|
}.should_not raise_error
|
168
|
-
pool.instance_variable_get(:@pool).length.should eql(
|
169
|
-
failed.should be_false # Make sure our requests woked
|
156
|
+
pool.instance_variable_get(:@pool).length.should eql(10) # make sure work got done
|
170
157
|
end
|
171
158
|
end
|
172
159
|
|
173
160
|
context "other http client" do
|
174
161
|
before(:each) do
|
175
|
-
@
|
176
|
-
@pool = HotTub::Pool.new(:clean => lambda {|clnt| clnt.clean}) {MocClient.new(@url)}
|
162
|
+
@pool = HotTub::Pool.new(:clean => lambda {|clnt| clnt.clean}) {MocClient.new}
|
177
163
|
end
|
178
164
|
|
179
165
|
it "should clean connections" do
|
@@ -183,6 +169,39 @@ describe HotTub::Pool do
|
|
183
169
|
end
|
184
170
|
end
|
185
171
|
|
172
|
+
context 'Excon' do
|
173
|
+
before(:each) do
|
174
|
+
@pool = HotTub::Pool.new(:size => 10) { Excon.new('https://www.google.com')}
|
175
|
+
end
|
176
|
+
it "should work" do
|
177
|
+
result = nil
|
178
|
+
@pool.run{|clnt| result = clnt.head.status}
|
179
|
+
result.should eql(200)
|
180
|
+
end
|
181
|
+
context 'threads' do
|
182
|
+
it "should work" do
|
183
|
+
url = "https://www.google.com/"
|
184
|
+
failed = false
|
185
|
+
threads = []
|
186
|
+
lambda {
|
187
|
+
20.times.each do
|
188
|
+
threads << Thread.new do
|
189
|
+
@pool.run{|connection| Thread.current[:status] = connection.head.status }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
sleep(0.01)
|
193
|
+
threads.each do |t|
|
194
|
+
t.join
|
195
|
+
end
|
196
|
+
}.should_not raise_error
|
197
|
+
@pool.instance_variable_get(:@pool).length.should eql(10) # make sure work got done
|
198
|
+
results = threads.collect{ |t| t[:status]}
|
199
|
+
results.length.should eql(20) # make sure all threads are present
|
200
|
+
results.uniq.should eql([200]) # make sure all returned status 200
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
186
205
|
unless HotTub.jruby?
|
187
206
|
context 'EM:HTTPRequest' do
|
188
207
|
before(:each) do
|
data/spec/session_spec.rb
CHANGED
@@ -1,123 +1,140 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'hot_tub/session'
|
3
3
|
require 'uri'
|
4
|
+
require 'time'
|
4
5
|
describe HotTub::Session do
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
context 'initialized without a block' do
|
8
|
+
it "should raise error if block is not supplied" do
|
9
|
+
lambda {HotTub::Session.new}.should raise_error(ArgumentError)
|
10
|
+
end
|
8
11
|
end
|
9
|
-
|
10
|
-
context 'default settings' do
|
12
|
+
context 'initialized with a block' do
|
11
13
|
before(:each) do
|
12
|
-
@url = "
|
13
|
-
@
|
14
|
-
@
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should have :size of 5" do
|
18
|
-
@options[:size].should eql(5)
|
14
|
+
@url = "https://www.somewebsite.com"
|
15
|
+
@uri = URI(@url)
|
16
|
+
@sessions = HotTub::Session.new { |url| MocClient.new(url) }
|
19
17
|
end
|
20
18
|
|
21
|
-
|
22
|
-
|
19
|
+
context 'default options' do
|
20
|
+
describe ':with_pool' do
|
21
|
+
it "with_pool should be true" do
|
22
|
+
@sessions.instance_variable_get(:@options)[:with_pool].should be_true
|
23
|
+
end
|
24
|
+
end
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
describe '#to_url' do
|
28
|
+
context "passed URL string" do
|
29
|
+
it "should return key with URI scheme-domain" do
|
30
|
+
@sessions.send(:to_key,@url).should eql("#{@uri.scheme}-#{@uri.host}")
|
31
|
+
end
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
34
|
+
context "passed URI" do
|
35
|
+
it "should return key with URI scheme-domain" do
|
36
|
+
@sessions.send(:to_key,@uri).should eql("#{@uri.scheme}-#{@uri.host}")
|
37
|
+
end
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
40
|
+
context "invalid argument" do
|
41
|
+
it "should raise an ArgumentError" do
|
42
|
+
lambda { @sessions.send(:to_key, nil) }.should raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
it "should raise URI::InvalidURIError with bad url" do
|
45
|
+
lambda { @sessions.send(:to_key,"bad url") }.should raise_error(URI::InvalidURIError)
|
46
|
+
end
|
47
|
+
end
|
39
48
|
end
|
40
49
|
|
41
|
-
|
42
|
-
@options[:never_block].should be_false
|
43
|
-
end
|
44
|
-
end
|
50
|
+
describe '#sessions' do
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
it "should add a new pool for the url" do
|
54
|
-
@tub.sessions(@url)
|
55
|
-
sessions = @tub.instance_variable_get(:@sessions)
|
56
|
-
sessions.length.should eql(1)
|
57
|
-
sessions.first[1].should be_a(HotTub::Pool)
|
58
|
-
end
|
52
|
+
context ':with_pool is true (default)' do
|
53
|
+
it "should add a new pool for the url" do
|
54
|
+
@sessions.sessions(@url)
|
55
|
+
sessions = @sessions.instance_variable_get(:@sessions)
|
56
|
+
sessions.length.should eql(1)
|
57
|
+
sessions.first[1].should be_a(HotTub::Pool)
|
58
|
+
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
it "should pass options to pool" do
|
61
|
+
with_pool_options = HotTub::Session.new(:size => 13, :never_block => false) { |url| MocClient.new(url) }
|
62
|
+
with_pool_options.sessions(@url)
|
63
|
+
sessions = with_pool_options.instance_variable_get(:@sessions)
|
64
|
+
pool_options = sessions.first[1].instance_variable_get(:@options)
|
65
|
+
pool_options[:size].should eql(13)
|
66
|
+
pool_options[:never_block].should be_false
|
67
|
+
end
|
65
68
|
end
|
66
|
-
end
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
context ':with_pool is false' do
|
71
|
+
it "should add a new client for the url" do
|
72
|
+
no_pool = HotTub::Session.new(:with_pool => false) { |url| MocClient.new(url) }
|
73
|
+
no_pool.sessions(@url)
|
74
|
+
sessions = no_pool.instance_variable_get(:@sessions)
|
75
|
+
sessions.length.should eql(1)
|
76
|
+
sessions.first[1].should be_a(MocClient)
|
77
|
+
end
|
73
78
|
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context "invalid argument" do
|
77
|
-
it "should raise an ArgumentError" do
|
78
|
-
lambda { @tub.sessions(nil) }.should raise_error(ArgumentError)
|
79
79
|
|
80
|
+
context "passed URL string" do
|
81
|
+
it "should set key with URI scheme-domain" do
|
82
|
+
@sessions.sessions(@url)
|
83
|
+
sessions = @sessions.instance_variable_get(:@sessions)
|
84
|
+
sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(HotTub::Pool)
|
85
|
+
end
|
80
86
|
end
|
81
|
-
|
82
|
-
|
87
|
+
context "passed URI" do
|
88
|
+
it "should set key with URI scheme-domain" do
|
89
|
+
@sessions.sessions(@uri)
|
90
|
+
sessions = @sessions.instance_variable_get(:@sessions)
|
91
|
+
sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(HotTub::Pool)
|
92
|
+
end
|
83
93
|
end
|
84
94
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
95
|
+
|
96
|
+
describe '#run' do
|
97
|
+
it "should work" do
|
98
|
+
@url = "https://www.somewebsite.com"
|
99
|
+
@sessions = HotTub::Session.new { |url| MocClient.new(url) }
|
100
|
+
result = nil
|
101
|
+
@sessions.run(@url) do |conn|
|
102
|
+
result = conn.get
|
103
|
+
end
|
104
|
+
result.should_not be_nil
|
93
105
|
end
|
94
|
-
status.should eql(200)
|
95
106
|
end
|
96
|
-
end
|
97
107
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
108
|
+
context 'thread safety' do
|
109
|
+
it "should work" do
|
110
|
+
url = "https://www.somewebsite.com/"
|
111
|
+
url2 = "http://www.someotherwebsit.com/"
|
112
|
+
session = HotTub::Session.new { |url| MocClient.new(url)}
|
113
|
+
failed = false
|
114
|
+
start_time = Time.now
|
115
|
+
stop_time = nil
|
116
|
+
mutex = Mutex.new
|
105
117
|
threads = []
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
118
|
+
lambda {
|
119
|
+
10.times.each do
|
120
|
+
threads << Thread.new do
|
121
|
+
# MocClient is not thread safe so lets initialize a new instance for each
|
122
|
+
session.run(url) { |clnt| Thread.current[:result] = MocClient.new(url).get }
|
123
|
+
session.run(url2) { |clnt| Thread.current[:result] = MocClient.new(url2).get }
|
124
|
+
end
|
110
125
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
126
|
+
threads.each do |t|
|
127
|
+
t.join
|
128
|
+
end
|
129
|
+
stop_time = Time.now
|
130
|
+
}.should_not raise_error # make sure we're thread safe
|
131
|
+
# Some extra checks just to make sure...
|
132
|
+
results = threads.collect{ |t| t[:result]}
|
133
|
+
results.length.should eql(10) # make sure all threads are present
|
134
|
+
results.uniq.should eql([results.first]) # make sure we got the same results
|
135
|
+
((stop_time.to_i - start_time.to_i) < (results.length * MocClient.sleep_time)).should be_true # make sure IO is running parallel
|
136
|
+
session.instance_variable_get(:@sessions).keys.length.should eql(2) # make sure sessions were created
|
137
|
+
end
|
121
138
|
end
|
122
139
|
end
|
123
140
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@ require 'hot_tub'
|
|
2
2
|
require 'rspec'
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'logger'
|
5
|
-
|
5
|
+
require 'excon'
|
6
6
|
# Requires supporting files with custom matchers and macros, etc,
|
7
7
|
# in ./support/ and its subdirectories.
|
8
8
|
#Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
@@ -18,23 +18,30 @@ class MocClient
|
|
18
18
|
@clean = false
|
19
19
|
end
|
20
20
|
|
21
|
+
# Perform an IO
|
21
22
|
def get
|
22
|
-
|
23
|
+
return `sleep #{self.class.sleep_time}; echo "that was slow IO"`
|
23
24
|
end
|
24
25
|
|
25
26
|
def close
|
26
|
-
|
27
|
+
@close = true
|
27
28
|
end
|
28
29
|
|
29
30
|
def closed?
|
30
|
-
|
31
|
+
@close == true
|
31
32
|
end
|
32
33
|
|
33
34
|
def clean
|
34
|
-
|
35
|
+
@clean = true
|
35
36
|
end
|
36
37
|
|
37
38
|
def cleaned?
|
38
|
-
|
39
|
+
@clean == true
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def sleep_time
|
44
|
+
0.5
|
45
|
+
end
|
39
46
|
end
|
40
47
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hot_tub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: httpclient
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '0'
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ! '>='
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '0'
|
30
14
|
- !ruby/object:Gem::Dependency
|
31
15
|
name: rspec
|
32
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +65,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
65
|
version: '0'
|
82
66
|
segments:
|
83
67
|
- 0
|
84
|
-
hash:
|
68
|
+
hash: 1359205392366777656
|
85
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
70
|
none: false
|
87
71
|
requirements:
|
@@ -90,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
74
|
version: '0'
|
91
75
|
segments:
|
92
76
|
- 0
|
93
|
-
hash:
|
77
|
+
hash: 1359205392366777656
|
94
78
|
requirements: []
|
95
79
|
rubyforge_project: hot_tub
|
96
80
|
rubygems_version: 1.8.25
|