hot_tub 0.3.0 → 0.4.0
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.
- checksums.yaml +5 -13
- data/.travis.yml +6 -9
- data/HISTORY.md +8 -1
- data/LICENSE.txt +1 -1
- data/README.md +11 -23
- data/benchmarks/hot_tub.rb +116 -0
- data/hot_tub.gemspec +3 -1
- data/lib/hot_tub/known_clients.rb +6 -4
- data/lib/hot_tub/pool.rb +65 -61
- data/lib/hot_tub/reaper.rb +14 -5
- data/lib/hot_tub/sessions.rb +33 -60
- data/lib/hot_tub/version.rb +1 -1
- data/lib/hot_tub.rb +3 -2
- data/spec/helpers/moc_pool.rb +1 -0
- data/spec/helpers/server.rb +16 -14
- data/spec/hot_tub/integration/excon_spec.rb +139 -0
- data/spec/hot_tub/integration/net_http_spec.rb +104 -0
- data/spec/hot_tub/integration/sessions_spec.rb +40 -0
- data/spec/hot_tub/pool_spec.rb +247 -0
- data/spec/{reaper_mixin_spec.rb → hot_tub/reaper_mixin_spec.rb} +7 -8
- data/spec/hot_tub/reaper_spec.rb +27 -0
- data/spec/hot_tub/sessions_spec.rb +137 -0
- data/spec/hot_tub_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -3
- metadata +63 -28
- data/spec/pool_spec.rb +0 -386
- data/spec/reaper_spec.rb +0 -28
- data/spec/sessions_spec.rb +0 -223
data/lib/hot_tub/sessions.rb
CHANGED
@@ -4,12 +4,12 @@ module HotTub
|
|
4
4
|
include HotTub::KnownClients
|
5
5
|
include HotTub::Reaper::Mixin
|
6
6
|
|
7
|
-
# HotTub::Session is a ThreadSafe::Cache where URLs are mapped
|
8
|
-
#
|
7
|
+
# HotTub::Session is a ThreadSafe::Cache where URLs are mapped HotTub::Pools.
|
9
8
|
#
|
10
|
-
#
|
9
|
+
#
|
10
|
+
# == Example:
|
11
11
|
# You can initialize a HotTub::Pool with each client by passing :with_pool as true and any pool options
|
12
|
-
# sessions = HotTub::Sessions.new(:
|
12
|
+
# sessions = HotTub::Sessions.new(:size => 12) {
|
13
13
|
# uri = URI.parse("http://somewebservice.com")
|
14
14
|
# http = Net::HTTP.new(uri.host, uri.port)
|
15
15
|
# http.use_ssl = false
|
@@ -25,26 +25,7 @@ module HotTub
|
|
25
25
|
# p conn.head('/').code
|
26
26
|
# end
|
27
27
|
#
|
28
|
-
# == Excon clients are initialized to a specific domain. Its sometimes useful
|
29
|
-
# to have the options of initializing Excon connections after startup, in
|
30
|
-
# a thread safe manner for multiple urls with a single object.
|
31
|
-
# Example:
|
32
|
-
#
|
33
|
-
# sessions = HotTub::Sessions.new { |url| Excon.new(url) }
|
34
|
-
#
|
35
|
-
# sessions.run("http://wwww.yahoo.com") do |conn|
|
36
|
-
# p conn.head.status
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# sessions.run("https://wwww.google.com") do |conn|
|
40
|
-
# p conn.head.status
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
#
|
44
28
|
# === OPTIONS
|
45
|
-
# [:with_pool]
|
46
|
-
# Default is false. A boolean that if true, wraps the new_client block in HotTub::Pool.new. All options
|
47
|
-
# are passed to the new HotTub::Pool object. See HotTub::Pool for options
|
48
29
|
# [:close]
|
49
30
|
# Default is nil. Can be a symbol representing an method to call on a client to close the client or a lambda
|
50
31
|
# that accepts the client as a parameter that will close a client. The close option is performed on clients
|
@@ -63,7 +44,6 @@ module HotTub
|
|
63
44
|
#
|
64
45
|
def initialize(opts={},&new_client)
|
65
46
|
raise ArgumentError, "HotTub::Sessions require a block on initialization that accepts a single argument" unless block_given?
|
66
|
-
@with_pool = opts[:with_pool] # Set to true to use HotTub::Pool with supplied new_client block
|
67
47
|
@close_client = opts[:close] # => lambda {|clnt| clnt.close}
|
68
48
|
@clean_client = opts[:clean] # => lambda {|clnt| clnt.clean}
|
69
49
|
@reap_client = opts[:reap] # => lambda {|clnt| clnt.reap?} # should return boolean
|
@@ -72,72 +52,65 @@ module HotTub
|
|
72
52
|
@shutdown = false
|
73
53
|
@reap_timeout = (opts[:reap_timeout] || 600) # the interval to reap connections in seconds
|
74
54
|
@reaper = Reaper.spawn(self) unless opts[:no_reaper]
|
75
|
-
@pool_options = {:no_reaper => true}.merge(opts)
|
55
|
+
@pool_options = {:no_reaper => true}.merge(opts)
|
76
56
|
at_exit {drain!}
|
77
57
|
end
|
78
58
|
|
79
|
-
# Safely initializes
|
59
|
+
# Safely initializes sessions
|
80
60
|
# expects a url string or URI
|
81
61
|
def session(url)
|
82
62
|
key = to_key(url)
|
83
63
|
return @sessions.get(key) if @sessions.get(key)
|
84
|
-
|
85
|
-
@
|
86
|
-
|
87
|
-
}
|
88
|
-
else
|
89
|
-
@sessions.compute_if_absent(key) {@new_client.call(url)}
|
90
|
-
end
|
64
|
+
@sessions.compute_if_absent(key) {
|
65
|
+
HotTub::Pool.new(@pool_options) { @new_client.call(url) }
|
66
|
+
}
|
91
67
|
@sessions.get(key)
|
92
68
|
end
|
93
69
|
alias :sessions :session
|
94
70
|
|
95
71
|
def run(url,&block)
|
96
72
|
session = sessions(url)
|
97
|
-
|
98
|
-
block.call(sessions(url))
|
73
|
+
session.run(&block) if session
|
99
74
|
end
|
100
75
|
|
101
76
|
def clean!
|
102
|
-
@sessions.each_pair do |key,
|
103
|
-
|
104
|
-
clnt.clean!
|
105
|
-
else
|
106
|
-
clean_client(clnt)
|
107
|
-
end
|
77
|
+
@sessions.each_pair do |key,pool|
|
78
|
+
pool.clean!
|
108
79
|
end
|
80
|
+
@sessions
|
109
81
|
end
|
110
82
|
|
111
83
|
def drain!
|
112
|
-
@sessions.each_pair do |key,
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
84
|
+
@sessions.each_pair do |key,pool|
|
85
|
+
pool.drain!
|
86
|
+
end
|
87
|
+
@sessions
|
88
|
+
end
|
89
|
+
|
90
|
+
def reset!
|
91
|
+
@sessions.each_pair do |key,pool|
|
92
|
+
pool.reset!
|
118
93
|
end
|
119
94
|
@sessions.clear
|
95
|
+
@sessions = ThreadSafe::Cache.new
|
96
|
+
@sessions
|
120
97
|
end
|
121
98
|
|
122
99
|
def shutdown!
|
123
|
-
@
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
100
|
+
@shutdown = true
|
101
|
+
begin
|
102
|
+
kill_reaper
|
103
|
+
ensure
|
104
|
+
drain!
|
105
|
+
@sessions = nil
|
129
106
|
end
|
130
|
-
|
107
|
+
nil
|
131
108
|
end
|
132
109
|
|
133
110
|
# Remove and close extra clients
|
134
111
|
def reap!
|
135
|
-
@sessions.each_pair do |key,
|
136
|
-
|
137
|
-
clnt.reap!
|
138
|
-
else
|
139
|
-
close_client(clnt) if reap_client?(clnt)
|
140
|
-
end
|
112
|
+
@sessions.each_pair do |key,pool|
|
113
|
+
pool.reap!
|
141
114
|
end
|
142
115
|
end
|
143
116
|
|
data/lib/hot_tub/version.rb
CHANGED
data/lib/hot_tub.rb
CHANGED
@@ -8,7 +8,8 @@ require "hot_tub/pool"
|
|
8
8
|
require "hot_tub/sessions"
|
9
9
|
|
10
10
|
module HotTub
|
11
|
-
@@logger =
|
11
|
+
@@logger = nil
|
12
|
+
|
12
13
|
def self.logger
|
13
14
|
@@logger
|
14
15
|
end
|
@@ -22,7 +23,7 @@ module HotTub
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.rbx?
|
25
|
-
defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx'
|
26
|
+
(defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx')
|
26
27
|
end
|
27
28
|
|
28
29
|
def self.new(opts={},&client_block)
|
data/spec/helpers/moc_pool.rb
CHANGED
data/spec/helpers/server.rb
CHANGED
@@ -15,24 +15,26 @@ module HotTub
|
|
15
15
|
set :server, 'puma'
|
16
16
|
set :port, 9595
|
17
17
|
|
18
|
-
get '/
|
19
|
-
(
|
18
|
+
get '/fast' do
|
19
|
+
sleep(0.01)
|
20
|
+
"foo"
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
-
|
23
|
+
get '/slow' do
|
24
|
+
sleep(1)
|
25
|
+
"foooooooooooo"
|
24
26
|
end
|
25
27
|
|
26
|
-
def self.
|
27
|
-
|
28
|
+
def self.teardown
|
29
|
+
@server.stop(true) if @server
|
28
30
|
end
|
29
31
|
|
30
|
-
def self.
|
31
|
-
'/
|
32
|
+
def self.url
|
33
|
+
'http://127.0.0.1:9595/fast'
|
32
34
|
end
|
33
35
|
|
34
|
-
def self.
|
35
|
-
'http://127.0.0.1:9595'
|
36
|
+
def self.slow_url
|
37
|
+
'http://127.0.0.1:9595/slow'
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -50,9 +52,9 @@ module HotTub
|
|
50
52
|
set :server, 'puma'
|
51
53
|
set :port, 9393
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
get '/fast' do
|
56
|
+
sleep(0.01)
|
57
|
+
"foo"
|
56
58
|
end
|
57
59
|
|
58
60
|
def self.teardown
|
@@ -60,7 +62,7 @@ module HotTub
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def self.url
|
63
|
-
'http://127.0.0.1:9393/
|
65
|
+
'http://127.0.0.1:9393/fast'
|
64
66
|
end
|
65
67
|
end
|
66
68
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HotTub do
|
4
|
+
|
5
|
+
context "blocking (size equals max_size)" do
|
6
|
+
let(:pool) do
|
7
|
+
HotTub.new(:size => 4, :max_size => 4) {
|
8
|
+
Excon.new(HotTub::Server.url, :thread_safe_sockets => false)
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:threads) { [] }
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
5.times do
|
16
|
+
excon_thread_work(pool, 30, threads)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it { expect(pool.current_size).to eql(4) }# make sure the pool grew beyond size
|
21
|
+
|
22
|
+
it "should work" do
|
23
|
+
results = threads.collect{ |t| t[:status]}
|
24
|
+
expect(results.length).to eql(150) # make sure all threads are present
|
25
|
+
expect(results.uniq).to eql([200]) # make sure all returned status 200
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should shutdown" do
|
29
|
+
pool.shutdown!
|
30
|
+
expect(pool.current_size).to eql(0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with larger max" do
|
35
|
+
let(:pool) do
|
36
|
+
HotTub.new(:size => 4, :max_size => 8) {
|
37
|
+
Excon.new(HotTub::Server.url, :thread_safe_sockets => false)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:threads) { [] }
|
42
|
+
|
43
|
+
before(:each) do
|
44
|
+
5.times do
|
45
|
+
excon_thread_work(pool, 30, threads)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it { expect(pool.current_size).to be >= 4 }
|
50
|
+
|
51
|
+
it { expect(pool.current_size).to be <= 8 }
|
52
|
+
|
53
|
+
it "should reap" do
|
54
|
+
pool.reap!
|
55
|
+
expect(pool.current_size).to eql(4)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should work" do
|
59
|
+
results = threads.collect{ |t| t[:status]}
|
60
|
+
expect(results.length).to eql(150) # make sure all threads are present
|
61
|
+
expect(results.uniq).to eql([200]) # make sure all returned status 200
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "sized without max" do
|
66
|
+
let(:pool) do
|
67
|
+
HotTub.new(:size => 4) {
|
68
|
+
Excon.new(HotTub::Server.url, :thread_safe_sockets => false)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
let(:threads) { [] }
|
73
|
+
|
74
|
+
before(:each) do
|
75
|
+
5.times do
|
76
|
+
excon_thread_work(pool, 30, threads)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it { expect(pool.current_size).to be > 4 }# make sure the pool grew beyond size
|
81
|
+
|
82
|
+
it "should reap" do
|
83
|
+
pool.reap!
|
84
|
+
expect(pool.current_size).to eql(4)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should work" do
|
88
|
+
results = threads.collect{ |t| t[:status]}
|
89
|
+
expect(results.length).to eql(150) # make sure all threads are present
|
90
|
+
expect(results.uniq).to eql([200]) # make sure all returned status 200
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "shutdown with slow client" do
|
95
|
+
let(:pool) do
|
96
|
+
HotTub.new(:size => 1) {
|
97
|
+
Excon.new(HotTub::Server.slow_url, :thread_safe_sockets => false, :read_timeout => 2)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should work" do
|
102
|
+
conn = nil
|
103
|
+
|
104
|
+
begin
|
105
|
+
th = Thread.new do
|
106
|
+
uri = URI.parse(HotTub::Server.slow_url)
|
107
|
+
pool.run do |connection|
|
108
|
+
conn = connection
|
109
|
+
connection.get(:path => uri.path).status
|
110
|
+
end
|
111
|
+
end
|
112
|
+
sleep(0.01)
|
113
|
+
pool.shutdown!
|
114
|
+
th.join
|
115
|
+
rescue => e
|
116
|
+
puts e.message
|
117
|
+
end
|
118
|
+
|
119
|
+
expect(pool.shutdown).to eql(true)
|
120
|
+
expect(pool.current_size).to eql(0)
|
121
|
+
expect(conn.send(:sockets)).to be_empty
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
def excon_thread_work(pool,thread_count=0, threads=[])
|
130
|
+
thread_count.times.each do
|
131
|
+
threads << Thread.new do
|
132
|
+
uri = URI.parse(HotTub::Server.url)
|
133
|
+
pool.run{|connection| Thread.current[:status] = connection.get(:path => uri.path).status }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
threads.each do |t|
|
137
|
+
t.join
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HotTub do
|
4
|
+
context "blocking (size equals max_size)" do
|
5
|
+
let(:pool) do
|
6
|
+
HotTub.new(:size => 4, :max_size => 4) {
|
7
|
+
uri = URI.parse(HotTub::Server.url)
|
8
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
9
|
+
http.use_ssl = false
|
10
|
+
http.start
|
11
|
+
http
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:threads) { [] }
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
5.times do
|
19
|
+
net_http_thread_work(pool, 30, threads)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it { expect(pool.current_size).to eql(4) } # make sure the pool grew beyond size
|
24
|
+
|
25
|
+
it "should work" do
|
26
|
+
results = threads.collect{ |t| t[:status]}
|
27
|
+
expect(results.length).to eql(150) # make sure all threads are present
|
28
|
+
expect(results.uniq).to eql(['200']) # make sure all returned status 200
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should shutdown" do
|
32
|
+
pool.shutdown!
|
33
|
+
expect(pool.current_size).to eql(0)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with larger max" do
|
38
|
+
let(:pool) do
|
39
|
+
HotTub.new(:size => 4, :max_size => 8) {
|
40
|
+
uri = URI.parse(HotTub::Server.url)
|
41
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
42
|
+
http.use_ssl = false
|
43
|
+
http.start
|
44
|
+
http
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
let(:threads) { [] }
|
49
|
+
|
50
|
+
before(:each) do
|
51
|
+
5.times do
|
52
|
+
net_http_thread_work(pool, 30, threads)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it { expect(pool.current_size).to be >= 4 }
|
57
|
+
it { expect(pool.current_size).to be <= 8 }
|
58
|
+
it "should work" do
|
59
|
+
results = threads.collect{ |t| t[:status]}
|
60
|
+
expect(results.length).to eql(150) # make sure all threads are present
|
61
|
+
expect(results.uniq).to eql(['200']) # make sure all returned status 200
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "sized without max" do
|
66
|
+
let(:pool) do
|
67
|
+
HotTub.new(:size => 4) {
|
68
|
+
uri = URI.parse(HotTub::Server.url)
|
69
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
70
|
+
http.use_ssl = false
|
71
|
+
http.start
|
72
|
+
http
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:threads) { [] }
|
77
|
+
|
78
|
+
before(:each) do
|
79
|
+
5.times do
|
80
|
+
net_http_thread_work(pool, 30, threads)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it { expect(pool.current_size).to be > 4 } # make sure the pool grew beyond size
|
85
|
+
|
86
|
+
it "should work" do
|
87
|
+
results = threads.collect{ |t| t[:status]}
|
88
|
+
expect(results.length).to eql(150) # make sure all threads are present
|
89
|
+
expect(results.uniq).to eql(['200']) # make sure all returned status 200
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def net_http_thread_work(pool,thread_count=0, threads=[])
|
95
|
+
thread_count.times.each do
|
96
|
+
threads << Thread.new do
|
97
|
+
uri = URI.parse(HotTub::Server.url)
|
98
|
+
pool.run{|connection| Thread.current[:status] = connection.get(uri.path).code }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
threads.each do |t|
|
102
|
+
t.join
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HotTub, 'with sessions' do
|
4
|
+
|
5
|
+
let(:url) { HotTub::Server.url }
|
6
|
+
let(:url2) { HotTub::Server2.url }
|
7
|
+
let(:sessions) do
|
8
|
+
HotTub.new(:sessions => true) { |url|
|
9
|
+
uri = URI.parse(url)
|
10
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
11
|
+
http.use_ssl = false
|
12
|
+
http.start
|
13
|
+
http
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:threads) { [] }
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
10.times.each do
|
21
|
+
threads << Thread.new do
|
22
|
+
sessions.run(url) { |clnt| Thread.current[:result] = clnt.get(URI.parse(url).path).code }
|
23
|
+
sessions.run(url2) { |clnt| Thread.current[:result] = clnt.get(URI.parse(url).path).code }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
threads.each do |t|
|
27
|
+
t.join
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should work" do
|
32
|
+
results = threads.collect{ |t| t[:result]}
|
33
|
+
expect(results.length).to eql(10)
|
34
|
+
expect(results.uniq).to eql([results.first])
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should work" do
|
38
|
+
expect(sessions.instance_variable_get(:@sessions).keys.length).to eql(2)
|
39
|
+
end
|
40
|
+
end
|