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 CHANGED
@@ -5,6 +5,7 @@ gemspec
5
5
  gem 'rake'
6
6
 
7
7
  group :development do
8
+ gem 'excon'
8
9
  platform :ruby do
9
10
  gem 'eventmachine'
10
11
  gem 'em-http-request', '~> 1.0', :require => 'em-http'
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. Supports [HTTPClient](https://github.com/nahi/httpclient) (default) and
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
- ### Default (HTTPClient)
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(@@url,query).body
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 you various domains based on URI.
67
+ seperate pools for your various domains based on URI. Options are passed to the pool when each pool is initialized.
68
68
 
69
- # Assuming EM is running
70
- require 'hot_tub/clients/em_http_request_client'
71
- class EMClass
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| HotTub::EmHttpRequestClient.new(url,{:connect_timeout => 5}) }
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
- "HTTPClient" => {
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
- # Generic lazy connection pool of HTTP clients
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 (HTTPClient)
35
- # pool = HotTub::Pool.new(:size => 25)
36
- # pool.run {|clnt| clnt.get('http://test.com').body }
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 with different client
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('http://test.com').body }
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 = (block_given? ? client_block : lambda { HTTPClient.new })
54
+ @client_block = client_block
60
55
  @options = {
61
56
  :size => 5,
62
57
  :never_block => true, # Return new client if we run out
@@ -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 HotTub::Pools by their domain.
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. Sessions
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("https://wwww.yahoo.com") do |conn|
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(:client_class => MyClient, :client_options => {:connect_timeout => 10})
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
- # Synchronize access to our key hash
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
- key = "#{uri.scheme}-#{uri.host}"
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
@@ -1,3 +1,3 @@
1
1
  module HotTub
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
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 default :client" do
22
- @pool.instance_variable_get(:@client_block).call.should be_a(HTTPClient)
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
- status = 0
74
- @pool.run{|clnt| status = clnt.head('https://www.google.com').status}
75
- status.should eql(200)
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 driver" do
125
- @pool.send(:client).should be_instance_of(HTTPClient)
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 => 20})
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| failed = true unless connection.head(url).status == 200}
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(20) # make sure work got done
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
- @url = "https://www.google.com"
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
- it "should raise error if block is not supplied" do
7
- lambda {HotTub::Session.new}.should raise_error(ArgumentError)
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 = "http://www.testurl123.com/"
13
- @tub = HotTub::Session.new { |url| MocClient.new(url) }
14
- @options = @tub.instance_variable_get(:@options)
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
- it "should have :blocking_timeout of 10 seconds" do
22
- @options[:blocking_timeout].should eql(10)
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
- it "should default never_block to true" do
26
- @options[:never_block].should be_true
27
- end
28
- end
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
- context 'passed options' do
31
- before(:each) do
32
- @url = "http://www.testurl123.com/"
33
- @tub = HotTub::Session.new({:size => 21, :never_block => false}) { |url| MocClient.new(url) }
34
- @options = @tub.instance_variable_get(:@options)
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
- it "should have @pool_size of 21" do
38
- @options[:size].should eql(21)
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
- it "should have never_block be false" do
42
- @options[:never_block].should be_false
43
- end
44
- end
50
+ describe '#sessions' do
45
51
 
46
- describe '#sessions' do
47
- before(:each) do
48
- @url = "https://www.google.com"
49
- @uri = URI(@url)
50
- @tub = HotTub::Session.new({:size => 21, :never_block => false}) { |url| MocClient.new(url) }
51
- end
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
- context "passed URL string" do
61
- it "should set key with URI scheme-domain" do
62
- @tub.sessions(@url)
63
- sessions = @tub.instance_variable_get(:@sessions)
64
- sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(HotTub::Pool)
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
- context "passed URI" do
69
- it "should set key with URI scheme-domain" do
70
- @tub.sessions(@uri)
71
- sessions = @tub.instance_variable_get(:@sessions)
72
- sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(HotTub::Pool)
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
- it "should raise URI::InvalidURIError with bad url" do
82
- lambda { @tub.sessions("bad url") }.should raise_error(URI::InvalidURIError)
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
- end
86
- describe '#run' do
87
- it "should work" do
88
- @url = "https://www.google.com"
89
- @tub = HotTub::Session.new({:size => 21, :never_block => false}) { |url| HTTPClient.new }
90
- status = 0
91
- @tub.run(@url) do |conn|
92
- status = conn.head(@url).status
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
- context 'thread safety' do
99
- it "should work" do
100
- url = "https://www.google.com/"
101
- url2 = "https://www.yahoo.com/"
102
- session = HotTub::Session.new({:size => 20}) { |a_url| HTTPClient.new}
103
- failed = false
104
- lambda {
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
- 20.times.each do
107
- threads << Thread.new do
108
- session.run(url){|connection| failed = true unless connection.head(url).status == 200}
109
- session.run(url2){|connection| failed = true unless connection.head(url).status == 200}
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
- end
112
- sleep(0.01)
113
- threads.each do |t|
114
- t.join
115
- end
116
- }.should_not raise_error
117
- session.instance_variable_get(:@sessions).keys.length.should eql(2) # make sure work got done
118
- session.instance_variable_get(:@sessions).values.first.instance_variable_get(:@pool).length.should eql(20) # make sure work got done
119
- session.instance_variable_get(:@sessions).values.last.instance_variable_get(:@pool).length.should eql(20) # make sure work got done
120
- failed.should be_false # Make sure our requests woked
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
- sleep(0.05)
23
+ return `sleep #{self.class.sleep_time}; echo "that was slow IO"`
23
24
  end
24
25
 
25
26
  def close
26
- @close = true
27
+ @close = true
27
28
  end
28
29
 
29
30
  def closed?
30
- @close == true
31
+ @close == true
31
32
  end
32
33
 
33
34
  def clean
34
- @clean = true
35
+ @clean = true
35
36
  end
36
37
 
37
38
  def cleaned?
38
- @clean == true
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.1.1
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-02 00:00:00.000000000 Z
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: 518626986870371684
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: 518626986870371684
77
+ hash: 1359205392366777656
94
78
  requirements: []
95
79
  rubyforge_project: hot_tub
96
80
  rubygems_version: 1.8.25