hot_tub 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +3 -0
- data/Gemfile +0 -1
- data/README.md +78 -75
- data/hot_tub.gemspec +4 -0
- data/lib/hot_tub/known_clients.rb +47 -0
- data/lib/hot_tub/pool.rb +39 -69
- data/lib/hot_tub/session.rb +36 -21
- data/lib/hot_tub/version.rb +1 -1
- data/lib/hot_tub.rb +5 -1
- data/spec/helpers/moc_client.rb +33 -0
- data/spec/helpers/server.rb +38 -0
- data/spec/pool_spec.rb +75 -14
- data/spec/session_spec.rb +58 -31
- data/spec/spec_helper.rb +11 -36
- metadata +57 -4
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# HotTub [](https://travis-ci.org/JoshMcKin/hot_tub)
|
|
2
|
-
A simple thread-safe connection
|
|
2
|
+
A simple thread-safe connection pool and sessions 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
|
-
There are a couple Ruby connection
|
|
5
|
-
|
|
4
|
+
There are a couple Ruby connection pooling libraries out there but HotTub differs from most with all its features.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
### HotTub::Pool
|
|
9
|
+
* Thread safe
|
|
10
|
+
* Lazy clients/connections (created only when necessary)
|
|
11
|
+
* Can be used with any client library
|
|
12
|
+
* Support for cleaning dirty resources
|
|
13
|
+
* Set to expand pool under load that is eventually reaped back down to set size (never_block), can be disabled
|
|
14
|
+
* Attempts to close clients/connections at_exit
|
|
15
|
+
|
|
16
|
+
### HotTub::Sessions
|
|
17
|
+
* Thread safe
|
|
18
|
+
* The same api as HotTub::Pool
|
|
19
|
+
* Can be used with HotTub::Pool or any client library
|
|
20
|
+
* Attempts to close clients/connections at_exit
|
|
6
21
|
|
|
7
22
|
## Installation
|
|
8
23
|
|
|
@@ -10,105 +25,93 @@ HotTub is available through [Rubygems](https://rubygems.org/gems/hot_tub) and ca
|
|
|
10
25
|
|
|
11
26
|
$ gem install hot_tub
|
|
12
27
|
|
|
13
|
-
##
|
|
28
|
+
## Rails setup
|
|
14
29
|
|
|
15
|
-
|
|
30
|
+
Add hot_tub to your gemfile:
|
|
31
|
+
|
|
32
|
+
gem 'hot_tub'
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@@pool = HotTub::Pool.new({:size => 10}) { Excon.new("http://somewebservice.com") }
|
|
21
|
-
def self.fetch_results(url,query={})
|
|
22
|
-
@@pool.run |connection|
|
|
23
|
-
connection.get(:query => query).body
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
MyClass.fetch_results({:foo => "goo"}) # => "Some reponse"
|
|
34
|
+
Run bundle:
|
|
35
|
+
|
|
36
|
+
bundle install
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
Configure Logger by creating a hot_tub.rb initializer and adding the following:
|
|
39
|
+
|
|
40
|
+
HotTub.logger = Rails.logger
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@@pool = HotTub::Pool.new(:size => 12) { EM::HttpRequest.new("http://somewebservice.com") }
|
|
35
|
-
def async_post_results(query = {})
|
|
36
|
-
@@pool.run do |connection|
|
|
37
|
-
connection.aget(:query => results, :keepalive => true)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
42
|
+
# Usage
|
|
43
|
+
|
|
44
|
+
## HotTub::Pool
|
|
41
45
|
|
|
46
|
+
### EM-Http-Request
|
|
47
|
+
require 'hot_tub'
|
|
48
|
+
require 'em-synchrony'
|
|
49
|
+
require 'em-synchrony/em-http'
|
|
42
50
|
EM.synchrony do {
|
|
43
|
-
|
|
51
|
+
pool = HotTub::Pool.new(:size => 12) { EM::HttpRequest.new("http://somewebservice.com") }
|
|
52
|
+
pool.run { |clnt| clnt.aget(:query => results, :keepalive => true) }
|
|
44
53
|
EM.stop
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
### Other
|
|
48
|
-
|
|
49
|
-
lambdas, if they are not defined they are ignored.
|
|
57
|
+
You can use any library you want with HotTub::Pool, regardless of that clients thread-safety status.
|
|
58
|
+
Close and clean can be defined at initialization with lambdas, if they are not defined they are ignored.
|
|
50
59
|
|
|
60
|
+
url = "http://test12345.com"
|
|
61
|
+
pool = HotTub::Pool.new({:size => 10, :close => lambda {|clnt| clnt.close}}) { MyHttpLib.new }
|
|
62
|
+
pool.run { |clnt| clnt.get(@@url,query).body }
|
|
63
|
+
|
|
64
|
+
## HotTub::Sessions Usage
|
|
65
|
+
HotTub::Sessions are a synchronized hash of clients/pools and are implemented similar HotTub::Pool.
|
|
66
|
+
For example, Excon is thread safe but you set a single url at the client level so sessions
|
|
67
|
+
are handy if you need to access multiple urls but would prefer a single object.
|
|
68
|
+
|
|
69
|
+
require 'hot_tub'
|
|
51
70
|
require 'excon'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
# Our client block must accept the url argument
|
|
72
|
+
sessions = HotTub::Sessions.new {|url| Excon.new(url) }
|
|
73
|
+
|
|
74
|
+
sessions.run("http://somewebservice.com") do |clnt|
|
|
75
|
+
puts clnt.get(:query => {:some => 'stuff'}).response_header.status
|
|
76
|
+
end
|
|
77
|
+
sessions.run("https://someotherwebservice.com") do |clnt|
|
|
78
|
+
puts clnt.get(:query => {:other => 'stuff'}).response_header.status
|
|
79
|
+
end
|
|
80
|
+
sessions.run("https://127.0.0.1:5252") do |clnt|
|
|
81
|
+
puts clnt.get.response_header.status
|
|
60
82
|
end
|
|
61
83
|
|
|
62
|
-
|
|
84
|
+
### HotTub::Sessions with HotTub::Pool
|
|
85
|
+
Suppose you have a client that is not thread safe, you can use HotTub::Pool with HotTub::Sessions to get what you need.
|
|
86
|
+
|
|
87
|
+
require 'hot_tub'
|
|
88
|
+
require "em-synchrony"
|
|
89
|
+
require "em-synchrony/em-http"
|
|
90
|
+
# Our client block must accept the url argument
|
|
63
91
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
can change paths the client domain cannot change. HotTub::Session allows you create a session object that initializes
|
|
67
|
-
seperate pools for your various domains based on URI. Options are passed to the pool when each pool is initialized.
|
|
92
|
+
EM.synchrony do {
|
|
93
|
+
sessions = HotTub::Sessions.new {|url| HotTub::Pool.new(:size => 12) { EM::HttpRequest.new(url, :inactivity_timeout => 0) }}
|
|
68
94
|
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
95
|
+
sessions.run("http://somewebservice.com") do |clnt|
|
|
96
|
+
puts clnt.get(:query => results).response_header.status
|
|
80
97
|
end
|
|
81
|
-
|
|
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
|
|
89
|
-
# Our client block must accept the url argument
|
|
90
|
-
@@sessons = HotTub::Sessions.new(:with_pool => false) {|url| Excon.new(url) }
|
|
91
|
-
def async_post_results(query = {})
|
|
92
|
-
@@sessons.run("http://somewebservice.com") do |connection|
|
|
93
|
-
puts connection.get(:query => results).response_header.status
|
|
94
|
-
end
|
|
95
|
-
@@sessons.run("https://someotherwebservice.com") do |connection|
|
|
96
|
-
puts connection.get(:query => results).response_header.status
|
|
97
|
-
end
|
|
98
|
+
sessions.run("https://someotherwebservice.com") do |clnt|
|
|
99
|
+
puts clnt.get(:query => results).response_header.status
|
|
98
100
|
end
|
|
99
|
-
|
|
101
|
+
EM.stop
|
|
102
|
+
}
|
|
100
103
|
|
|
101
104
|
## Related
|
|
102
105
|
|
|
103
106
|
* [EM-Http-Request](https://github.com/igrigorik/em-http-request)
|
|
104
107
|
* [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.
|
|
108
|
+
* [Excon](https://github.com/geemus/excon) Thread safe with its own built in pool.
|
|
109
|
+
* [HTTPClient](https://github.com/nahi/httpclient) A thread safe http client that supports pools and sessions all by itself.
|
|
107
110
|
|
|
108
111
|
## Other Pooling Gem
|
|
109
112
|
|
|
110
113
|
* [ConnectionPool](https://github.com/mperham/connection_pool)
|
|
111
|
-
* [EM-Synchrony](https://github.com/igrigorik/em-synchrony) has a
|
|
114
|
+
* [EM-Synchrony](https://github.com/igrigorik/em-synchrony) has a pool feature
|
|
112
115
|
|
|
113
116
|
## Contributing to HotTub
|
|
114
117
|
|
data/hot_tub.gemspec
CHANGED
|
@@ -13,7 +13,11 @@ 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
|
+
|
|
16
17
|
s.add_development_dependency "rspec"
|
|
18
|
+
s.add_development_dependency "sinatra"
|
|
19
|
+
s.add_development_dependency "puma"
|
|
20
|
+
s.add_development_dependency "excon"
|
|
17
21
|
|
|
18
22
|
s.files = `git ls-files`.split("\n")
|
|
19
23
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module HotTub
|
|
2
|
+
module KnownClients
|
|
3
|
+
KNOWN_CLIENTS = {
|
|
4
|
+
"Excon::Connection" => {
|
|
5
|
+
:close => lambda { |clnt| clnt.reset }
|
|
6
|
+
},
|
|
7
|
+
'EventMachine::HttpConnection' => {
|
|
8
|
+
:close => lambda { |clnt|
|
|
9
|
+
if clnt.conn
|
|
10
|
+
clnt.conn.close_connection
|
|
11
|
+
clnt.instance_variable_set(:@deferred, true)
|
|
12
|
+
end
|
|
13
|
+
},
|
|
14
|
+
:clean => lambda { |clnt|
|
|
15
|
+
if clnt.conn && clnt.conn.error?
|
|
16
|
+
HotTub.logger.info "Sanitizing connection : #{EventMachine::report_connection_error_status(clnt.conn.instance_variable_get(:@signature))}"
|
|
17
|
+
clnt.conn.close_connection
|
|
18
|
+
clnt.instance_variable_set(:@deferred, true)
|
|
19
|
+
end
|
|
20
|
+
clnt
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
attr_accessor :options
|
|
25
|
+
# Attempts to clean the provided client, checking the options first for a clean block
|
|
26
|
+
# then checking the known clients
|
|
27
|
+
def clean_client(clnt)
|
|
28
|
+
return @options[:clean].call(clnt) if @options && @options[:clean] && @options[:clean].is_a?(Proc)
|
|
29
|
+
if settings = KNOWN_CLIENTS[clnt.class.name]
|
|
30
|
+
settings[:clean].call(clnt) if settings[:clean].is_a?(Proc)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Attempts to close the provided client, checking the options first for a close block
|
|
35
|
+
# then checking the known clients
|
|
36
|
+
def close_client(clnt)
|
|
37
|
+
return @options[:close].call(clnt) if @options && @options[:close] && @options[:close].is_a?(Proc)
|
|
38
|
+
if settings = KNOWN_CLIENTS[clnt.class.name]
|
|
39
|
+
begin
|
|
40
|
+
settings[:close].call(clnt) if settings[:close].is_a?(Proc)
|
|
41
|
+
rescue => e
|
|
42
|
+
HotTub.logger.error "There was an error close one of your #{self.class.name} connections: #{e}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/hot_tub/pool.rb
CHANGED
|
@@ -1,49 +1,25 @@
|
|
|
1
1
|
module HotTub
|
|
2
2
|
class Pool
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
:close => lambda { |clnt| clnt.reset }
|
|
7
|
-
},
|
|
8
|
-
'EventMachine::HttpConnection' => {
|
|
9
|
-
:close => lambda { |clnt|
|
|
10
|
-
if clnt.conn
|
|
11
|
-
clnt.conn.close_connection
|
|
12
|
-
clnt.instance_variable_set(:@deferred, true)
|
|
13
|
-
end
|
|
14
|
-
},
|
|
15
|
-
:clean => lambda { |clnt|
|
|
16
|
-
if clnt.conn && clnt.conn.error?
|
|
17
|
-
HotTub.logger.info "Sanitizing connection : #{EventMachine::report_connection_error_status(clnt.conn.instance_variable_get(:@signature))}"
|
|
18
|
-
clnt.conn.close_connection
|
|
19
|
-
clnt.instance_variable_set(:@deferred, true)
|
|
20
|
-
end
|
|
21
|
-
clnt
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
3
|
+
include HotTub::KnownClients
|
|
4
|
+
attr_reader :current_size, :fetching_client, :last_activity
|
|
5
|
+
|
|
26
6
|
# Thread-safe lazy connection pool
|
|
27
7
|
#
|
|
28
|
-
# == Example Excon
|
|
29
|
-
# pool = HotTub::Pool.new(:size => 25) { Excon.new('http://test.com') }
|
|
30
|
-
# pool.run {|clnt| clnt.get.body }
|
|
31
|
-
#
|
|
32
8
|
# == Example EM-Http-Request
|
|
33
9
|
# pool = HotTub::Pool.new { EM::HttpRequest.new("http://somewebservice.com") }
|
|
34
10
|
# pool.run {|clnt| clnt.get(:keepalive => true).body }
|
|
35
11
|
#
|
|
36
|
-
# HotTub::Pool defaults never_block to true, which means if run out of
|
|
12
|
+
# HotTub::Pool defaults never_block to true, which means if we run out of
|
|
37
13
|
# connections simply create a new client to continue operations.
|
|
38
|
-
# The pool
|
|
39
|
-
#
|
|
40
|
-
#
|
|
14
|
+
# The pool will grow and extra connections will be resued until activity dies down.
|
|
15
|
+
# If you would like to block and possibly throw an exception rather than temporarily
|
|
16
|
+
# grow the set :size, set :never_block to false; blocking_timeout defaults to 10 seconds.
|
|
41
17
|
#
|
|
42
18
|
# == Example without #never_block (will BlockingTimeout exception)
|
|
43
|
-
# pool = HotTub::Pool.new(:size => 1, :never_block => false, :blocking_timeout => 0.5) {
|
|
19
|
+
# pool = HotTub::Pool.new(:size => 1, :never_block => false, :blocking_timeout => 0.5) { EM::HttpRequest.new("http://somewebservice.com") }
|
|
44
20
|
#
|
|
45
21
|
# begin
|
|
46
|
-
# pool.run {|clnt| clnt.get.body }
|
|
22
|
+
# pool.run {|clnt| clnt.get(:keepalive => true).body }
|
|
47
23
|
# rescue HotTub::BlockingTimeout => e
|
|
48
24
|
# puts "Our pool ran out: {e}"
|
|
49
25
|
# end
|
|
@@ -61,8 +37,9 @@ module HotTub
|
|
|
61
37
|
}.merge(options)
|
|
62
38
|
@pool = []
|
|
63
39
|
@current_size = 0
|
|
64
|
-
@
|
|
65
|
-
@
|
|
40
|
+
@mutex = (HotTub.em_synchrony? ? EM::Synchrony::Thread::Mutex.new : Mutex.new)
|
|
41
|
+
@last_activity = Time.now
|
|
42
|
+
@fetching_client = false
|
|
66
43
|
end
|
|
67
44
|
|
|
68
45
|
# Hand off to client.run
|
|
@@ -80,13 +57,12 @@ module HotTub
|
|
|
80
57
|
# Calls close on all connections and reset the pools
|
|
81
58
|
def close_all
|
|
82
59
|
@mutex.synchronize do
|
|
83
|
-
while clnt = @
|
|
60
|
+
while clnt = @pool.pop
|
|
84
61
|
begin
|
|
85
62
|
close_client(clnt)
|
|
86
63
|
rescue => e
|
|
87
64
|
HotTub.logger.error "There was an error close one of your HotTub::Pool connections: #{e}"
|
|
88
65
|
end
|
|
89
|
-
@pool.delete(clnt)
|
|
90
66
|
end
|
|
91
67
|
@current_size = 0
|
|
92
68
|
end
|
|
@@ -107,26 +83,8 @@ module HotTub
|
|
|
107
83
|
clnt
|
|
108
84
|
end
|
|
109
85
|
|
|
110
|
-
# Attempts to clean the provided client, checking the options first for a clean block
|
|
111
|
-
# then checking the known clients
|
|
112
|
-
def clean_client(clnt)
|
|
113
|
-
return @options[:clean].call(clnt) if @options[:clean] if @options[:clean].is_a?(Proc)
|
|
114
|
-
if settings = KNOWN_CLIENTS[clnt.class.name]
|
|
115
|
-
settings[:clean].call(clnt) if settings[:clean].is_a?(Proc)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Attempts to close the provided client, checking the options first for a close block
|
|
120
|
-
# then checking the known clients
|
|
121
|
-
def close_client(clnt)
|
|
122
|
-
return @options[:close].call(clnt) if @options[:close] if @options[:close].is_a?(Proc)
|
|
123
|
-
if settings = KNOWN_CLIENTS[clnt.class.name]
|
|
124
|
-
settings[:close].call(clnt) if settings[:close].is_a?(Proc)
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
86
|
def raise_alarm
|
|
129
|
-
message = "Could not fetch a free client in time. Consider increasing your pool size
|
|
87
|
+
message = "Could not fetch a free client in time. Consider increasing your pool size."
|
|
130
88
|
HotTub.logger.error message
|
|
131
89
|
raise BlockingTimeout, message
|
|
132
90
|
end
|
|
@@ -134,47 +92,59 @@ module HotTub
|
|
|
134
92
|
# Safely add client back to pool
|
|
135
93
|
def push(clnt)
|
|
136
94
|
@mutex.synchronize do
|
|
137
|
-
|
|
138
|
-
@pool << clnt
|
|
139
|
-
else
|
|
140
|
-
@clients.delete(clnt)
|
|
141
|
-
close_client(clnt)
|
|
142
|
-
end
|
|
95
|
+
@pool << clnt
|
|
143
96
|
end
|
|
144
97
|
nil # make sure never return the pool
|
|
145
98
|
end
|
|
146
99
|
|
|
147
100
|
# Safely pull client from pool, adding if allowed
|
|
148
101
|
def pop
|
|
102
|
+
@fetching_client = true # kill reap_pool
|
|
149
103
|
@mutex.synchronize do
|
|
150
104
|
add if add?
|
|
151
|
-
clnt = @pool.pop
|
|
105
|
+
clnt = @pool.pop # get warm connection
|
|
152
106
|
if (clnt.nil? && @options[:never_block])
|
|
153
|
-
|
|
154
|
-
clnt =
|
|
107
|
+
add
|
|
108
|
+
clnt = @pool.pop
|
|
155
109
|
end
|
|
110
|
+
@fetching_client = false
|
|
156
111
|
clnt
|
|
157
112
|
end
|
|
113
|
+
ensure
|
|
114
|
+
reap_pool if reap_pool?
|
|
158
115
|
end
|
|
159
116
|
|
|
160
117
|
# create a new client from base client
|
|
161
118
|
def new_client
|
|
162
|
-
|
|
163
|
-
@clients << clnt
|
|
164
|
-
clnt
|
|
119
|
+
@client_block.call
|
|
165
120
|
end
|
|
166
121
|
|
|
167
122
|
# Only want to add a client if the pool is empty in keeping with
|
|
168
123
|
# a lazy model.
|
|
169
124
|
def add?
|
|
170
|
-
(@pool.length == 0 && @
|
|
125
|
+
(@pool.length == 0 && (@options[:size] > @current_size))
|
|
171
126
|
end
|
|
172
127
|
|
|
173
128
|
def add
|
|
174
129
|
HotTub.logger.info "Adding HotTub client: #{@client.class.name} to pool"
|
|
130
|
+
@last_activity = Time.now
|
|
175
131
|
@current_size += 1
|
|
176
132
|
@pool << new_client
|
|
177
133
|
end
|
|
134
|
+
|
|
135
|
+
def reap_pool?
|
|
136
|
+
(!@fetching_client && (@current_size > @options[:size]) && ((@last_activity + (600)) < Time.now))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Remove extra connections from front of pool
|
|
140
|
+
def reap_pool
|
|
141
|
+
@mutex.synchronize do
|
|
142
|
+
if reap_pool? && clnt = @pool.shift
|
|
143
|
+
@current_size -= 1
|
|
144
|
+
close_client(clnt)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
178
148
|
end
|
|
179
149
|
class BlockingTimeout < StandardError;end
|
|
180
150
|
end
|
data/lib/hot_tub/session.rb
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
require 'uri'
|
|
2
2
|
module HotTub
|
|
3
3
|
class Session
|
|
4
|
-
|
|
4
|
+
include HotTub::KnownClients
|
|
5
5
|
# A HotTub::Session is a synchronized hash used to separate pools/clients by their domain.
|
|
6
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.
|
|
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.
|
|
8
9
|
# Example:
|
|
9
10
|
#
|
|
10
|
-
# sessions = HotTub::Session.new
|
|
11
|
+
# sessions = HotTub::Session.new { |url| Excon.new(url) }
|
|
11
12
|
#
|
|
12
13
|
# sessions.run("http://wwww.yahoo.com") do |conn|
|
|
13
|
-
# p conn.head.
|
|
14
|
+
# p conn.head.status
|
|
14
15
|
# end
|
|
15
16
|
#
|
|
16
17
|
# sessions.run("https://wwww.google.com") do |conn|
|
|
17
|
-
# p conn.head.
|
|
18
|
+
# p conn.head.status
|
|
18
19
|
# end
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
-
# If you have your own client class you can use sessions but your client class must initialize similar to
|
|
22
|
-
# EmHttpRequest, accepting a URI and options see: hot_tub/clients/em_http_request_client.rb
|
|
23
|
-
# Example Custom Client:
|
|
21
|
+
# Example with Pool:
|
|
24
22
|
#
|
|
25
|
-
# sessions = HotTub::
|
|
23
|
+
# sessions = HotTub::Pool.new(:size => 12) { EM::HttpRequest.new("http://somewebservice.com") }
|
|
26
24
|
#
|
|
27
|
-
# sessions.run("
|
|
28
|
-
# p conn.head.response_header.status
|
|
25
|
+
# sessions.run("http://wwww.yahoo.com") do |conn|
|
|
26
|
+
# p conn.head.response_header.status
|
|
29
27
|
# end
|
|
30
28
|
#
|
|
31
29
|
# sessions.run("https://wwww.google.com") do |conn|
|
|
32
|
-
# p conn.head.response_header.status
|
|
30
|
+
# p conn.head.response_header.status
|
|
33
31
|
# end
|
|
32
|
+
#
|
|
34
33
|
def initialize(options={},&client_block)
|
|
35
34
|
raise ArgumentError, "HotTub::Sessions requre a block on initialization that accepts a single argument" unless block_given?
|
|
36
|
-
|
|
35
|
+
at_exit { close_all } # close connections at exit
|
|
36
|
+
@options = options || {}
|
|
37
37
|
@client_block = client_block
|
|
38
38
|
@sessions = Hash.new
|
|
39
|
-
@mutex = (HotTub.
|
|
39
|
+
@mutex = (HotTub.em_synchrony? ? EM::Synchrony::Thread::Mutex.new : Mutex.new)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Synchronizes initialization of our sessions
|
|
@@ -45,20 +45,35 @@ module HotTub
|
|
|
45
45
|
key = to_key(url)
|
|
46
46
|
return @sessions[key] if @sessions[key]
|
|
47
47
|
@mutex.synchronize do
|
|
48
|
-
if @
|
|
49
|
-
|
|
50
|
-
else
|
|
51
|
-
@sessions[key] ||= @client_block.call(url)
|
|
52
|
-
end
|
|
48
|
+
@sessions[key] = @client_block.call(url) if @sessions[key].nil?
|
|
49
|
+
@sessions[key]
|
|
53
50
|
end
|
|
54
51
|
end
|
|
55
52
|
|
|
56
53
|
def run(url,&block)
|
|
57
54
|
session = sessions(url)
|
|
58
|
-
return session.run(&block) if
|
|
55
|
+
return session.run(&block) if session.is_a?(HotTub::Pool)
|
|
59
56
|
block.call(sessions(url))
|
|
60
57
|
end
|
|
61
58
|
|
|
59
|
+
# Calls close on all pools/clients in sessions
|
|
60
|
+
def close_all
|
|
61
|
+
@sessions.each do |key,clnt|
|
|
62
|
+
if clnt.is_a?(HotTub::Pool)
|
|
63
|
+
clnt.close_all
|
|
64
|
+
else
|
|
65
|
+
begin
|
|
66
|
+
close_client(clnt)
|
|
67
|
+
rescue => e
|
|
68
|
+
HotTub.logger.error "There was an error close one of your HotTub::Session clients: #{e}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
@mutex.synchronize do
|
|
72
|
+
@sessions[key] = nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
62
77
|
private
|
|
63
78
|
|
|
64
79
|
def to_key(url)
|
data/lib/hot_tub/version.rb
CHANGED
data/lib/hot_tub.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'thread'
|
|
2
|
-
require 'timeout'
|
|
3
2
|
require 'logger'
|
|
4
3
|
require "hot_tub/version"
|
|
4
|
+
require "hot_tub/known_clients"
|
|
5
5
|
require "hot_tub/pool"
|
|
6
6
|
require "hot_tub/session"
|
|
7
7
|
|
|
@@ -19,6 +19,10 @@ module HotTub
|
|
|
19
19
|
(defined?(EM) && EM::reactor_running?)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def self.em_synchrony?
|
|
23
|
+
(self.em? && defined?(EM::Synchrony))
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
def self.jruby?
|
|
23
27
|
(defined?(JRUBY_VERSION))
|
|
24
28
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class MocClient
|
|
2
|
+
def initialize(url=nil,options={})
|
|
3
|
+
@close = false
|
|
4
|
+
@clean = false
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Perform an IO
|
|
8
|
+
def get
|
|
9
|
+
return `sleep #{self.class.sleep_time}; echo "that was slow IO"`
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def close
|
|
13
|
+
@close = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def closed?
|
|
17
|
+
@close == true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def clean
|
|
21
|
+
@clean = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def cleaned?
|
|
25
|
+
@clean == true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def sleep_time
|
|
30
|
+
0.2
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'sinatra'
|
|
2
|
+
require 'puma'
|
|
3
|
+
module HotTub
|
|
4
|
+
class Server < Sinatra::Base
|
|
5
|
+
|
|
6
|
+
def self.run
|
|
7
|
+
@events = Puma::Events.new STDOUT, STDERR
|
|
8
|
+
@server = Puma::Server.new HotTub::Server.new, @events
|
|
9
|
+
@server.min_threads = 10
|
|
10
|
+
@server.max_threads = 100
|
|
11
|
+
@server.add_tcp_listener '127.0.0.1', 9595
|
|
12
|
+
@server.run
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
set :server, 'puma'
|
|
16
|
+
set :port, 9595
|
|
17
|
+
|
|
18
|
+
get '/data/:amount' do |amount|
|
|
19
|
+
(('x' * amount.to_i ) << Random.new.rand(0..999999).to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.teardown
|
|
23
|
+
@server.stop(true) if @server
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.size
|
|
27
|
+
10_000
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.path
|
|
31
|
+
'/data/' << size.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.url
|
|
35
|
+
'http://127.0.0.1:9595' << path
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/spec/pool_spec.rb
CHANGED
|
@@ -87,10 +87,8 @@ describe HotTub::Pool do
|
|
|
87
87
|
end
|
|
88
88
|
it "should reset pool" do
|
|
89
89
|
@pool.current_size.should eql(5)
|
|
90
|
-
@pool.instance_variable_get(:@clients).length.should eql(5)
|
|
91
90
|
@pool.instance_variable_get(:@pool).length.should eql(5)
|
|
92
91
|
@pool.close_all
|
|
93
|
-
@pool.instance_variable_get(:@clients).length.should eql(0)
|
|
94
92
|
@pool.instance_variable_get(:@pool).length.should eql(0)
|
|
95
93
|
@pool.current_size.should eql(0)
|
|
96
94
|
end
|
|
@@ -136,9 +134,57 @@ describe HotTub::Pool do
|
|
|
136
134
|
end
|
|
137
135
|
end
|
|
138
136
|
|
|
137
|
+
context ':never_block' do
|
|
138
|
+
context 'is true' do
|
|
139
|
+
it "should add connections to pool as necessary" do
|
|
140
|
+
pool = HotTub::Pool.new({:size => 1}) { MocClient.new }
|
|
141
|
+
threads = []
|
|
142
|
+
5.times.each do
|
|
143
|
+
threads << Thread.new do
|
|
144
|
+
pool.run{|connection| connection.get }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
sleep(0.01)
|
|
148
|
+
threads.each do |t|
|
|
149
|
+
t.join
|
|
150
|
+
end
|
|
151
|
+
(pool.current_size > 1).should be_true
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
context 'is false' do
|
|
155
|
+
it "should not add connections to pool beyond specified size" do
|
|
156
|
+
pool = HotTub::Pool.new({:size => 1, :never_block => false, :blocking_timeout => 10}) { MocClient.new }
|
|
157
|
+
threads = []
|
|
158
|
+
3.times.each do
|
|
159
|
+
threads << Thread.new do
|
|
160
|
+
pool.run{|connection| connection.get }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
sleep(0.01)
|
|
164
|
+
threads.each do |t|
|
|
165
|
+
t.join
|
|
166
|
+
end
|
|
167
|
+
pool.current_size.should eql(1)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe '#reap_pool' do
|
|
173
|
+
context 'current_size is greater than :size' do
|
|
174
|
+
it "should remove a connection from the pool" do
|
|
175
|
+
pool = HotTub::Pool.new({:size => 1}) { MocClient.new }
|
|
176
|
+
pool.instance_variable_set(:@last_activity,(Time.now - 601))
|
|
177
|
+
pool.instance_variable_set(:@pool, [MocClient.new,MocClient.new])
|
|
178
|
+
pool.instance_variable_set(:@current_size,2)
|
|
179
|
+
pool.send(:reap_pool)
|
|
180
|
+
pool.current_size.should eql(1)
|
|
181
|
+
pool.instance_variable_get(:@pool).length.should eql(1)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
139
186
|
context 'thread safety' do
|
|
140
187
|
it "should work" do
|
|
141
|
-
url = "https://www.google.com/"
|
|
142
188
|
pool = HotTub::Pool.new({:size => 10}) { MocClient.new }
|
|
143
189
|
failed = false
|
|
144
190
|
lambda {
|
|
@@ -153,7 +199,7 @@ describe HotTub::Pool do
|
|
|
153
199
|
t.join
|
|
154
200
|
end
|
|
155
201
|
}.should_not raise_error
|
|
156
|
-
pool.instance_variable_get(:@pool).length
|
|
202
|
+
(pool.instance_variable_get(:@pool).length >= 10).should be_true # make sure work got done
|
|
157
203
|
end
|
|
158
204
|
end
|
|
159
205
|
|
|
@@ -169,9 +215,9 @@ describe HotTub::Pool do
|
|
|
169
215
|
end
|
|
170
216
|
end
|
|
171
217
|
|
|
172
|
-
context 'Excon' do
|
|
218
|
+
context 'Excon' do # Excon has its own pool, but just need to test with a real non-EM library
|
|
173
219
|
before(:each) do
|
|
174
|
-
@pool = HotTub::Pool.new(:size => 10) { Excon.new(
|
|
220
|
+
@pool = HotTub::Pool.new(:size => 10) { Excon.new(HotTub::Server.url)}
|
|
175
221
|
end
|
|
176
222
|
it "should work" do
|
|
177
223
|
result = nil
|
|
@@ -180,11 +226,23 @@ describe HotTub::Pool do
|
|
|
180
226
|
end
|
|
181
227
|
context 'threads' do
|
|
182
228
|
it "should work" do
|
|
183
|
-
url = "https://www.google.com/"
|
|
184
229
|
failed = false
|
|
185
230
|
threads = []
|
|
186
231
|
lambda {
|
|
187
|
-
|
|
232
|
+
15.times.each do
|
|
233
|
+
threads << Thread.new do
|
|
234
|
+
@pool.run{|connection| Thread.current[:status] = connection.head.status }
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
sleep(0.01)
|
|
238
|
+
threads.each do |t|
|
|
239
|
+
t.join
|
|
240
|
+
end
|
|
241
|
+
}.should_not raise_error
|
|
242
|
+
# Reuse and run reaper
|
|
243
|
+
@pool.instance_variable_set(:@last_activity,(Time.now - 601))
|
|
244
|
+
lambda {
|
|
245
|
+
10.times.each do
|
|
188
246
|
threads << Thread.new do
|
|
189
247
|
@pool.run{|connection| Thread.current[:status] = connection.head.status }
|
|
190
248
|
end
|
|
@@ -194,9 +252,9 @@ describe HotTub::Pool do
|
|
|
194
252
|
t.join
|
|
195
253
|
end
|
|
196
254
|
}.should_not raise_error
|
|
197
|
-
@pool.instance_variable_get(:@pool).length
|
|
255
|
+
(@pool.instance_variable_get(:@pool).length == 10).should be_true # make sure work got done
|
|
198
256
|
results = threads.collect{ |t| t[:status]}
|
|
199
|
-
results.length.should eql(
|
|
257
|
+
results.length.should eql(25) # make sure all threads are present
|
|
200
258
|
results.uniq.should eql([200]) # make sure all returned status 200
|
|
201
259
|
end
|
|
202
260
|
end
|
|
@@ -205,7 +263,7 @@ describe HotTub::Pool do
|
|
|
205
263
|
unless HotTub.jruby?
|
|
206
264
|
context 'EM:HTTPRequest' do
|
|
207
265
|
before(:each) do
|
|
208
|
-
@url =
|
|
266
|
+
@url = HotTub::Server.url
|
|
209
267
|
end
|
|
210
268
|
|
|
211
269
|
it "should work" do
|
|
@@ -216,6 +274,7 @@ describe HotTub::Pool do
|
|
|
216
274
|
c.run { |conn| status << conn.ahead(:keepalive => true).response_header.status}
|
|
217
275
|
c.run { |conn| status << conn.head(:keepalive => true).response_header.status}
|
|
218
276
|
status.should eql([200,0,200])
|
|
277
|
+
c.close_all
|
|
219
278
|
EM.stop
|
|
220
279
|
end
|
|
221
280
|
end
|
|
@@ -223,14 +282,15 @@ describe HotTub::Pool do
|
|
|
223
282
|
context 'fibers' do
|
|
224
283
|
it "should work" do
|
|
225
284
|
EM.synchrony do
|
|
226
|
-
url = "https://www.google.com/"
|
|
227
285
|
pool = HotTub::Pool.new({:size => 5}) {EM::HttpRequest.new(@url)}
|
|
228
286
|
failed = false
|
|
229
287
|
fibers = []
|
|
230
288
|
lambda {
|
|
231
289
|
10.times.each do
|
|
232
290
|
fibers << Fiber.new do
|
|
233
|
-
pool.run{|connection|
|
|
291
|
+
pool.run{|connection|
|
|
292
|
+
s = connection.head(:keepalive => true).response_header.status
|
|
293
|
+
failed = true unless s == 200}
|
|
234
294
|
end
|
|
235
295
|
end
|
|
236
296
|
fibers.each do |f|
|
|
@@ -248,8 +308,9 @@ describe HotTub::Pool do
|
|
|
248
308
|
end
|
|
249
309
|
end
|
|
250
310
|
}.should_not raise_error
|
|
251
|
-
pool.instance_variable_get(:@pool).length
|
|
311
|
+
(pool.instance_variable_get(:@pool).length >= 5).should be_true #make sure work got done
|
|
252
312
|
failed.should be_false # Make sure our requests worked
|
|
313
|
+
pool.close_all
|
|
253
314
|
EM.stop
|
|
254
315
|
end
|
|
255
316
|
end
|
data/spec/session_spec.rb
CHANGED
|
@@ -2,6 +2,10 @@ require 'spec_helper'
|
|
|
2
2
|
require 'hot_tub/session'
|
|
3
3
|
require 'uri'
|
|
4
4
|
require 'time'
|
|
5
|
+
unless HotTub.jruby?
|
|
6
|
+
require "em-synchrony"
|
|
7
|
+
require "em-synchrony/em-http"
|
|
8
|
+
end
|
|
5
9
|
describe HotTub::Session do
|
|
6
10
|
|
|
7
11
|
context 'initialized without a block' do
|
|
@@ -16,14 +20,6 @@ describe HotTub::Session do
|
|
|
16
20
|
@sessions = HotTub::Session.new { |url| MocClient.new(url) }
|
|
17
21
|
end
|
|
18
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
|
|
25
|
-
end
|
|
26
|
-
|
|
27
23
|
describe '#to_url' do
|
|
28
24
|
context "passed URL string" do
|
|
29
25
|
it "should return key with URI scheme-domain" do
|
|
@@ -48,32 +44,23 @@ describe HotTub::Session do
|
|
|
48
44
|
end
|
|
49
45
|
|
|
50
46
|
describe '#sessions' do
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
60
|
-
it "should pass options to pool" do
|
|
61
|
-
with_pool_options = HotTub::Session.new(:size => 13, :never_block => false) { |url| MocClient.new(url) }
|
|
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) } }
|
|
62
50
|
with_pool_options.sessions(@url)
|
|
63
51
|
sessions = with_pool_options.instance_variable_get(:@sessions)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
pool_options[:never_block].should be_false
|
|
52
|
+
sessions.length.should eql(1)
|
|
53
|
+
sessions.first[1].should be_a(HotTub::Pool)
|
|
67
54
|
end
|
|
68
55
|
end
|
|
69
56
|
|
|
70
|
-
context '
|
|
57
|
+
context 'other clients' do
|
|
71
58
|
it "should add a new client for the url" do
|
|
72
|
-
no_pool = HotTub::Session.new
|
|
59
|
+
no_pool = HotTub::Session.new { |url| Excon.new(url) }
|
|
73
60
|
no_pool.sessions(@url)
|
|
74
61
|
sessions = no_pool.instance_variable_get(:@sessions)
|
|
75
62
|
sessions.length.should eql(1)
|
|
76
|
-
sessions.first[1].should be_a(
|
|
63
|
+
sessions.first[1].should be_a(Excon::Connection)
|
|
77
64
|
end
|
|
78
65
|
end
|
|
79
66
|
|
|
@@ -81,14 +68,14 @@ describe HotTub::Session do
|
|
|
81
68
|
it "should set key with URI scheme-domain" do
|
|
82
69
|
@sessions.sessions(@url)
|
|
83
70
|
sessions = @sessions.instance_variable_get(:@sessions)
|
|
84
|
-
sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(
|
|
71
|
+
sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(MocClient)
|
|
85
72
|
end
|
|
86
73
|
end
|
|
87
74
|
context "passed URI" do
|
|
88
75
|
it "should set key with URI scheme-domain" do
|
|
89
76
|
@sessions.sessions(@uri)
|
|
90
77
|
sessions = @sessions.instance_variable_get(:@sessions)
|
|
91
|
-
sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(
|
|
78
|
+
sessions["#{@uri.scheme}-#{@uri.host}"].should be_a(MocClient)
|
|
92
79
|
end
|
|
93
80
|
end
|
|
94
81
|
end
|
|
@@ -105,11 +92,11 @@ describe HotTub::Session do
|
|
|
105
92
|
end
|
|
106
93
|
end
|
|
107
94
|
|
|
108
|
-
context '
|
|
95
|
+
context 'threads' do
|
|
109
96
|
it "should work" do
|
|
110
|
-
url =
|
|
111
|
-
url2 = "http://www.
|
|
112
|
-
session = HotTub::Session.new { |url|
|
|
97
|
+
url = HotTub::Server.url
|
|
98
|
+
url2 = "http://www.yahoo.com/"
|
|
99
|
+
session = HotTub::Session.new { |url| Excon.new(url)}
|
|
113
100
|
failed = false
|
|
114
101
|
start_time = Time.now
|
|
115
102
|
stop_time = nil
|
|
@@ -136,5 +123,45 @@ describe HotTub::Session do
|
|
|
136
123
|
session.instance_variable_get(:@sessions).keys.length.should eql(2) # make sure sessions were created
|
|
137
124
|
end
|
|
138
125
|
end
|
|
126
|
+
|
|
127
|
+
unless HotTub.jruby?
|
|
128
|
+
context 'fibers' do
|
|
129
|
+
it "should work" do
|
|
130
|
+
EM.synchrony do
|
|
131
|
+
sessions = HotTub::Session.new {|url| HotTub::Pool.new({:size => 5}) {EM::HttpRequest.new(url)}}
|
|
132
|
+
failed = false
|
|
133
|
+
fibers = []
|
|
134
|
+
lambda {
|
|
135
|
+
10.times.each do
|
|
136
|
+
fibers << Fiber.new do
|
|
137
|
+
sessions.run(@url) {|connection|
|
|
138
|
+
s = connection.head(:keepalive => true).response_header.status
|
|
139
|
+
failed = true unless s == 200}
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
fibers.each do |f|
|
|
143
|
+
f.resume
|
|
144
|
+
end
|
|
145
|
+
loop do
|
|
146
|
+
done = true
|
|
147
|
+
fibers.each do |f|
|
|
148
|
+
done = false if f.alive?
|
|
149
|
+
end
|
|
150
|
+
if done
|
|
151
|
+
break
|
|
152
|
+
else
|
|
153
|
+
EM::Synchrony.sleep(0.01)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
}.should_not raise_error
|
|
157
|
+
sessions.instance_variable_get(:@sessions).keys.length.should eql(1)
|
|
158
|
+
(sessions.sessions(@url).instance_variable_get(:@pool).length >= 5).should be_true #make sure work got done
|
|
159
|
+
failed.should be_false # Make sure our requests worked
|
|
160
|
+
sessions.close_all
|
|
161
|
+
EM.stop
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
139
166
|
end
|
|
140
167
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -3,45 +3,20 @@ require 'rspec'
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
4
|
require 'logger'
|
|
5
5
|
require 'excon'
|
|
6
|
+
require 'helpers/moc_client'
|
|
7
|
+
require 'helpers/server'
|
|
8
|
+
require 'net/https'
|
|
9
|
+
|
|
6
10
|
# Requires supporting files with custom matchers and macros, etc,
|
|
7
11
|
# in ./support/ and its subdirectories.
|
|
8
12
|
#Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
9
13
|
HotTub.logger.level = Logger::ERROR
|
|
10
14
|
|
|
11
15
|
RSpec.configure do |config|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Perform an IO
|
|
22
|
-
def get
|
|
23
|
-
return `sleep #{self.class.sleep_time}; echo "that was slow IO"`
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def close
|
|
27
|
-
@close = true
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def closed?
|
|
31
|
-
@close == true
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def clean
|
|
35
|
-
@clean = true
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def cleaned?
|
|
39
|
-
@clean == true
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class << self
|
|
43
|
-
def sleep_time
|
|
44
|
-
0.5
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
16
|
+
config.before(:suite) do
|
|
17
|
+
HotTub::Server.run
|
|
18
|
+
end
|
|
19
|
+
config.after(:suite) do
|
|
20
|
+
HotTub::Server.teardown
|
|
21
|
+
end
|
|
22
|
+
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.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-04-
|
|
12
|
+
date: 2013-04-04 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rspec
|
|
@@ -27,6 +27,54 @@ dependencies:
|
|
|
27
27
|
- - ! '>='
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
29
|
version: '0'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: sinatra
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
type: :development
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: puma
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ! '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: excon
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
type: :development
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ! '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
30
78
|
description: A simple thread-safe http connection pooling gem. Http client options
|
|
31
79
|
include HTTPClient and EM-Http-Request
|
|
32
80
|
email:
|
|
@@ -44,9 +92,12 @@ files:
|
|
|
44
92
|
- Rakefile
|
|
45
93
|
- hot_tub.gemspec
|
|
46
94
|
- lib/hot_tub.rb
|
|
95
|
+
- lib/hot_tub/known_clients.rb
|
|
47
96
|
- lib/hot_tub/pool.rb
|
|
48
97
|
- lib/hot_tub/session.rb
|
|
49
98
|
- lib/hot_tub/version.rb
|
|
99
|
+
- spec/helpers/moc_client.rb
|
|
100
|
+
- spec/helpers/server.rb
|
|
50
101
|
- spec/pool_spec.rb
|
|
51
102
|
- spec/session_spec.rb
|
|
52
103
|
- spec/spec_helper.rb
|
|
@@ -65,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
65
116
|
version: '0'
|
|
66
117
|
segments:
|
|
67
118
|
- 0
|
|
68
|
-
hash:
|
|
119
|
+
hash: -1949508601036359576
|
|
69
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
121
|
none: false
|
|
71
122
|
requirements:
|
|
@@ -74,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
74
125
|
version: '0'
|
|
75
126
|
segments:
|
|
76
127
|
- 0
|
|
77
|
-
hash:
|
|
128
|
+
hash: -1949508601036359576
|
|
78
129
|
requirements: []
|
|
79
130
|
rubyforge_project: hot_tub
|
|
80
131
|
rubygems_version: 1.8.25
|
|
@@ -82,6 +133,8 @@ signing_key:
|
|
|
82
133
|
specification_version: 3
|
|
83
134
|
summary: A simple thread-safe http connection pooling gem.
|
|
84
135
|
test_files:
|
|
136
|
+
- spec/helpers/moc_client.rb
|
|
137
|
+
- spec/helpers/server.rb
|
|
85
138
|
- spec/pool_spec.rb
|
|
86
139
|
- spec/session_spec.rb
|
|
87
140
|
- spec/spec_helper.rb
|