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.
- checksums.yaml +15 -0
- data/.travis.yml +5 -5
- data/Gemfile +0 -4
- data/HISTORY.md +12 -4
- data/LICENSE.txt +1 -1
- data/README.md +94 -70
- data/hot_tub.gemspec +4 -2
- data/lib/hot_tub.rb +8 -13
- data/lib/hot_tub/known_clients.rb +41 -23
- data/lib/hot_tub/pool.rb +235 -98
- data/lib/hot_tub/reaper.rb +37 -0
- data/lib/hot_tub/sessions.rb +157 -0
- data/lib/hot_tub/version.rb +1 -1
- data/spec/helpers/moc_client.rb +10 -6
- data/spec/helpers/moc_pool.rb +23 -0
- data/spec/hot_tub_spec.rb +20 -0
- data/spec/pool_spec.rb +189 -178
- data/spec/reaper_mixin_spec.rb +29 -0
- data/spec/reaper_spec.rb +28 -0
- data/spec/sessions_spec.rb +223 -0
- data/spec/spec_helper.rb +1 -0
- metadata +34 -27
- data/lib/hot_tub/session.rb +0 -103
- data/spec/session_spec.rb +0 -213
data/lib/hot_tub/session.rb
DELETED
@@ -1,103 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
module HotTub
|
3
|
-
class Session
|
4
|
-
include HotTub::KnownClients
|
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. You can use any client
|
8
|
-
# you choose, but make sure you client is threadsafe.
|
9
|
-
# Example:
|
10
|
-
#
|
11
|
-
# sessions = HotTub::Session.new { |url| Excon.new(url) }
|
12
|
-
#
|
13
|
-
# sessions.run("http://wwww.yahoo.com") do |conn|
|
14
|
-
# p conn.head.status
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# sessions.run("https://wwww.google.com") do |conn|
|
18
|
-
# p conn.head.status
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# Example with Pool:
|
22
|
-
# You can initialize a HotTub::Pool with each client by passing :with_pool as true and any pool options
|
23
|
-
# sessions = HotTub::Session.new(:with_pool => true, :size => 12) { EM::HttpRequest.new("http://somewebservice.com") }
|
24
|
-
#
|
25
|
-
# sessions.run("http://wwww.yahoo.com") do |conn|
|
26
|
-
# p conn.head.response_header.status
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# sessions.run("https://wwww.google.com") do |conn|
|
30
|
-
# p conn.head.response_header.status
|
31
|
-
# end
|
32
|
-
#
|
33
|
-
#
|
34
|
-
def initialize(options={},&client_block)
|
35
|
-
raise ArgumentError, "HotTub::Sessions requre a block on initialization that accepts a single argument" unless block_given?
|
36
|
-
@options = options || {}
|
37
|
-
@client_block = client_block
|
38
|
-
@sessions = Hash.new
|
39
|
-
@mutex = (em_client? ? EM::Synchrony::Thread::Mutex.new : Mutex.new)
|
40
|
-
HotTub.hot_at_exit( em_client? ) {close_all}
|
41
|
-
end
|
42
|
-
|
43
|
-
# Synchronizes initialization of our sessions
|
44
|
-
# expects a url string or URI
|
45
|
-
def sessions(url)
|
46
|
-
key = to_key(url)
|
47
|
-
return @sessions[key] if @sessions[key]
|
48
|
-
@mutex.synchronize do
|
49
|
-
if @options[:with_pool]
|
50
|
-
@sessions[key] = HotTub::Pool.new(@options) { @client_block.call(url) }
|
51
|
-
else
|
52
|
-
@sessions[key] = @client_block.call(url) if @sessions[key].nil?
|
53
|
-
end
|
54
|
-
@sessions[key]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def run(url,&block)
|
59
|
-
session = sessions(url)
|
60
|
-
return session.run(&block) if session.is_a?(HotTub::Pool)
|
61
|
-
block.call(sessions(url))
|
62
|
-
end
|
63
|
-
|
64
|
-
# Calls close on all pools/clients in sessions
|
65
|
-
def close_all
|
66
|
-
@sessions.each do |key,clnt|
|
67
|
-
if clnt.is_a?(HotTub::Pool)
|
68
|
-
clnt.close_all
|
69
|
-
else
|
70
|
-
begin
|
71
|
-
close_client(clnt)
|
72
|
-
rescue => e
|
73
|
-
HotTub.logger.error "There was an error close one of your HotTub::Session clients: #{e}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
@mutex.synchronize do
|
77
|
-
@sessions[key] = nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def em_client?
|
85
|
-
begin
|
86
|
-
(HotTub.em_synchrony? && @client_block.call("http://moc").is_a?(EventMachine::HttpConnection))
|
87
|
-
rescue
|
88
|
-
false
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def to_key(url)
|
93
|
-
if url.is_a?(String)
|
94
|
-
uri = URI(url)
|
95
|
-
elsif url.is_a?(URI)
|
96
|
-
uri = url
|
97
|
-
else
|
98
|
-
raise ArgumentError, "you must pass a string or a URI object"
|
99
|
-
end
|
100
|
-
"#{uri.scheme}://#{uri.host}:#{uri.port}"
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
data/spec/session_spec.rb
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'hot_tub/session'
|
3
|
-
require 'uri'
|
4
|
-
require 'time'
|
5
|
-
unless HotTub.jruby?
|
6
|
-
require "em-synchrony"
|
7
|
-
require "em-synchrony/em-http"
|
8
|
-
end
|
9
|
-
describe HotTub::Session do
|
10
|
-
|
11
|
-
context 'initialized without a block' do
|
12
|
-
it "should raise error if block is not supplied" do
|
13
|
-
lambda {HotTub::Session.new}.should raise_error(ArgumentError)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
context 'initialized with a block' do
|
17
|
-
before(:each) do
|
18
|
-
@url = "https://www.somewebsite.com"
|
19
|
-
@uri = URI(@url)
|
20
|
-
@sessions = HotTub::Session.new { |url| MocClient.new(url) }
|
21
|
-
end
|
22
|
-
|
23
|
-
describe '#to_url' do
|
24
|
-
context "passed URL string" do
|
25
|
-
it "should return key with URI scheme-domain" do
|
26
|
-
@sessions.send(:to_key,@url).should eql("#{@uri.scheme}://#{@uri.host}:#{@uri.port}")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
context "passed URI" do
|
31
|
-
it "should return key with URI scheme-domain" do
|
32
|
-
@sessions.send(:to_key,@uri).should eql("#{@uri.scheme}://#{@uri.host}:#{@uri.port}")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
context "invalid argument" do
|
37
|
-
it "should raise an ArgumentError" do
|
38
|
-
lambda { @sessions.send(:to_key, nil) }.should raise_error(ArgumentError)
|
39
|
-
end
|
40
|
-
it "should raise URI::InvalidURIError with bad url" do
|
41
|
-
lambda { @sessions.send(:to_key,"bad url") }.should raise_error(URI::InvalidURIError)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe '#sessions' do
|
47
|
-
context 'HotTub::Pool as client' do
|
48
|
-
it "should add a new client for the url" do
|
49
|
-
with_pool_options = HotTub::Session.new { |url| HotTub::Pool.new(:size => 13) { MocClient.new(url) } }
|
50
|
-
with_pool_options.sessions(@url)
|
51
|
-
sessions = with_pool_options.instance_variable_get(:@sessions)
|
52
|
-
sessions.length.should eql(1)
|
53
|
-
sessions.first[1].should be_a(HotTub::Pool)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
context 'other clients' do
|
58
|
-
it "should add a new client for the url" do
|
59
|
-
no_pool = HotTub::Session.new { |url| Excon.new(url) }
|
60
|
-
no_pool.sessions(@url)
|
61
|
-
sessions = no_pool.instance_variable_get(:@sessions)
|
62
|
-
sessions.length.should eql(1)
|
63
|
-
sessions.first[1].should be_a(Excon::Connection)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
context "passed URL string" do
|
68
|
-
it "should set key with URI scheme-domain" do
|
69
|
-
@sessions.sessions(@url)
|
70
|
-
sessions = @sessions.instance_variable_get(:@sessions)
|
71
|
-
sessions["#{@uri.scheme}://#{@uri.host}:#{@uri.port}"].should be_a(MocClient)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
context "passed URI" do
|
75
|
-
it "should set key with URI scheme-domain" do
|
76
|
-
@sessions.sessions(@uri)
|
77
|
-
sessions = @sessions.instance_variable_get(:@sessions)
|
78
|
-
sessions["#{@uri.scheme}://#{@uri.host}:#{@uri.port}"].should be_a(MocClient)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
context "with_pool" do
|
83
|
-
it "should initialize a new HotTub::Pool" do
|
84
|
-
session_with_pool = HotTub::Session.new({:with_pool => true}) { |url| MocClient.new(url) }
|
85
|
-
pool = session_with_pool.sessions(@url)
|
86
|
-
pool.should be_a(HotTub::Pool)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
describe '#run' do
|
92
|
-
it "should work" do
|
93
|
-
url = HotTub::Server.url
|
94
|
-
sessions = HotTub::Session.new { |url| Excon.new(url) }
|
95
|
-
result = nil
|
96
|
-
sessions.run(url) do |conn|
|
97
|
-
result = conn.get.status
|
98
|
-
end
|
99
|
-
result.should eql(200)
|
100
|
-
end
|
101
|
-
|
102
|
-
context "with_pool" do
|
103
|
-
it "should pass run to pool" do
|
104
|
-
url = HotTub::Server.url
|
105
|
-
session_with_pool = HotTub::Session.new({:with_pool => true}) { |url| Excon.new(url) }
|
106
|
-
result = nil
|
107
|
-
session_with_pool.run(url) do |conn|
|
108
|
-
result = conn.get.status
|
109
|
-
end
|
110
|
-
result.should eql(200)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context 'threads' do
|
116
|
-
it "should work" do
|
117
|
-
url = HotTub::Server.url
|
118
|
-
url2 = HotTub::Server2.url
|
119
|
-
session = HotTub::Session.new(:with_pool => true) { |url| Excon.new(url)}
|
120
|
-
failed = false
|
121
|
-
start_time = Time.now
|
122
|
-
stop_time = nil
|
123
|
-
threads = []
|
124
|
-
lambda {
|
125
|
-
10.times.each do
|
126
|
-
threads << Thread.new do
|
127
|
-
# MocClient is not thread safe so lets initialize a new instance for each
|
128
|
-
session.run(url) { |clnt| Thread.current[:result] = clnt.get.status }
|
129
|
-
session.run(url2) { |clnt| Thread.current[:result] = clnt.get.status }
|
130
|
-
end
|
131
|
-
end
|
132
|
-
threads.each do |t|
|
133
|
-
t.join
|
134
|
-
end
|
135
|
-
stop_time = Time.now
|
136
|
-
}.should_not raise_error # make sure we're thread safe
|
137
|
-
# Some extra checks just to make sure...
|
138
|
-
results = threads.collect{ |t| t[:result]}
|
139
|
-
results.length.should eql(10) # make sure all threads are present
|
140
|
-
results.uniq.should eql([results.first]) # make sure we got the same results
|
141
|
-
((stop_time.to_i - start_time.to_i) < (results.length * MocClient.sleep_time)).should be_true # make sure IO is running parallel
|
142
|
-
session.instance_variable_get(:@sessions).keys.length.should eql(2) # make sure sessions were created
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
unless HotTub.jruby?
|
147
|
-
|
148
|
-
describe "em_client?" do
|
149
|
-
|
150
|
-
context 'EM::HttpRequest as client' do
|
151
|
-
before(:each) do
|
152
|
-
@session = HotTub::Session.new {|url| EM::HttpRequest.new(url)}
|
153
|
-
end
|
154
|
-
context "EM::Synchrony is present" do
|
155
|
-
it "should be true" do
|
156
|
-
HotTub.stub(:em_synchrony?).and_return(true)
|
157
|
-
@session.send(:em_client?).should be_true
|
158
|
-
end
|
159
|
-
end
|
160
|
-
context "EM::Synchrony is not present" do
|
161
|
-
it "should be false" do
|
162
|
-
HotTub.stub(:em_synchrony?).and_return(false)
|
163
|
-
@session.send(:em_client?).should be_false
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
context 'client is not EM::HttpRequest' do
|
168
|
-
it "should be false" do
|
169
|
-
session = HotTub::Session.new {|url| MocClient.new}
|
170
|
-
session.send(:em_client?).should be_false
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
context 'fibers' do
|
176
|
-
it "should work" do
|
177
|
-
EM.synchrony do
|
178
|
-
sessions = HotTub::Session.new(:with_pool => true) {|url| EM::HttpRequest.new(url)}
|
179
|
-
failed = false
|
180
|
-
fibers = []
|
181
|
-
lambda {
|
182
|
-
10.times.each do
|
183
|
-
fibers << Fiber.new do
|
184
|
-
sessions.run(@url) {|connection|
|
185
|
-
s = connection.head(:keepalive => true).response_header.status
|
186
|
-
failed = true unless s == 200}
|
187
|
-
end
|
188
|
-
end
|
189
|
-
fibers.each do |f|
|
190
|
-
f.resume
|
191
|
-
end
|
192
|
-
loop do
|
193
|
-
done = true
|
194
|
-
fibers.each do |f|
|
195
|
-
done = false if f.alive?
|
196
|
-
end
|
197
|
-
if done
|
198
|
-
break
|
199
|
-
else
|
200
|
-
EM::Synchrony.sleep(0.01)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
}.should_not raise_error
|
204
|
-
sessions.instance_variable_get(:@sessions).keys.length.should eql(1)
|
205
|
-
(sessions.sessions(@url).instance_variable_get(:@pool).length >= 5).should be_true #make sure work got done
|
206
|
-
failed.should be_false # Make sure our requests worked
|
207
|
-
EM.stop
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|