hot_tub 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZjAwZWMwNWJkNzE4ZDhlYTg2MzIyYjdlMGM1YTIxY2JkZWQ1ZGY3ZA==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 814f65fdb0c6f97026ed4dcabbe50d8deaab6a4c
|
4
|
+
data.tar.gz: 4c9744264dae35c80d089194de51e8c445193cdd
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ODQ2ZTBkMThmZmU4YjIxNTg3YWM1OGNlZWRmYTExOTFlY2JkOTYyMzM0MThm
|
11
|
-
M2U4ODI2MmE0NWI3ZmFiZmEwMzU0ZTdlZTY2NjIyMTMyMDdjMzM=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
M2QyMzI2OWY0MDBlZWYzYWYwYmVmNmIzMGMyZWVhOWZmYzk4NDEyNGYzYWIz
|
14
|
-
OWZkNmQwMDFmOTFhOTEzNDgwYTVmYzBlMTFjNjk0MTg4YzVmNzk2NjlkY2E3
|
15
|
-
YmNiOTUyYWE2OTNhYzFiY2EwYTc4MzhlZDg2OGYwMWU5NGE2Y2M=
|
6
|
+
metadata.gz: b343c114ffc19c435e44d981eebc68f85d31fa6f7f82461c75e95f292993bc6cf312a4172666b1aa85b1b4bb83be90002d5a347ac8bafd763d3511f0c57eaedd
|
7
|
+
data.tar.gz: 33e1b92fc75d10504679660ed36618d25dbe302e9229b0afba10a585fc5de9b3b4904088aed630f82bfb1eed11804573c6c7fefcdc73bcdc3f0a96be8ff75130
|
data/.travis.yml
CHANGED
data/HISTORY.md
CHANGED
@@ -4,7 +4,14 @@ HotTub Changelog
|
|
4
4
|
Head
|
5
5
|
=======
|
6
6
|
|
7
|
-
|
7
|
+
0.4.0
|
8
|
+
=======
|
9
|
+
- Hide HotTub::Sessions, should only be used with HotTub::Pool, otherwise just use ThreadSafe::Cache directly
|
10
|
+
- Reaper is now just a thread, and make sure we abort on exception
|
11
|
+
- add #reset! to Pool and Session for use after forking
|
12
|
+
- Test slow shutdowns
|
13
|
+
- Move integration tests and isolate
|
14
|
+
- General refactoring
|
8
15
|
|
9
16
|
0.3.0
|
10
17
|
=======
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -15,7 +15,7 @@ A thread safe, lazy pool.
|
|
15
15
|
* Support for closing resources on shutdown
|
16
16
|
|
17
17
|
### HotTub::Sessions
|
18
|
-
A [ThreadSafe::Cache](https://github.com/headius/thread_safe) where URLs are mapped to a
|
18
|
+
A [ThreadSafe::Cache](https://github.com/headius/thread_safe) where URLs are mapped to a HotTub::Pool
|
19
19
|
|
20
20
|
### Requirements
|
21
21
|
HotTub is tested on MRI, JRUBY and Rubinius
|
@@ -43,7 +43,6 @@ Configure Logger by creating `config\initializers\hot_tub.rb` and adding the fol
|
|
43
43
|
|
44
44
|
# Usage
|
45
45
|
|
46
|
-
## HotTub
|
47
46
|
For convenience you can initialize a new HotTub::Pool by calling HotTub.new or HotTub::Pool.new directly.
|
48
47
|
Returns an instance of HotTub::Pool.
|
49
48
|
|
@@ -69,6 +68,16 @@ Returns an instance of HotTub::Pool.
|
|
69
68
|
}
|
70
69
|
pool.run {|clnt| puts clnt.head('/').code }
|
71
70
|
|
71
|
+
### Excon
|
72
|
+
|
73
|
+
require 'hot_tub'
|
74
|
+
require 'excon'
|
75
|
+
|
76
|
+
pool = HotTub.new(:size => 10) {
|
77
|
+
Excon.new("http://somewebservice.com", :thread_safe_sockets => false)
|
78
|
+
}
|
79
|
+
pool.run {|clnt| puts clnt.head('/').status }
|
80
|
+
|
72
81
|
### HotTub Options
|
73
82
|
**size**: Default is 5. An integer that sets the size of the pool. Could be describe as minimum size the pool should grow to.
|
74
83
|
|
@@ -78,8 +87,6 @@ Returns an instance of HotTub::Pool.
|
|
78
87
|
|
79
88
|
**reap_timeout**: Default is 600 seconds. An integer that represents the timeout for reaping the pool in seconds.
|
80
89
|
|
81
|
-
**close_out**: Default is false. A boolean value that if true force close_client to be called on checkout clients when #drain! is called
|
82
|
-
|
83
90
|
**close**: Default is nil. Can be a symbol representing an method to call on a client to close the client or a lambda that accepts the client as a parameter that will close a client. The close option is performed on clients on reaping and shutdown after the client has been removed from the pool. When nil, as is the default, no action is performed.
|
84
91
|
|
85
92
|
**clean**: Default is nil. Can be a symbol representing an method to call on a client to clean the client or a lambda that accepts the client as a parameter that will clean a client. When nil, as is the default, no action is performed.
|
@@ -120,25 +127,6 @@ You can use any library you want with `HotTub::Pool`.
|
|
120
127
|
hot_tub = HotTub.new({:size => 10, :close => lambda {|clnt| clnt.close}, :clean => :clean, :reap => :reap?}) { MyHttpLib.new }
|
121
128
|
hot_tub.run { |clnt| clnt.get(url,query).body }
|
122
129
|
|
123
|
-
## Sessions only
|
124
|
-
Returns a `HotTub::Sessions` instance.
|
125
|
-
|
126
|
-
[Excon](https://github.com/geemus/excon) is thread safe but you set a single url at the client level so sessions
|
127
|
-
are handy if you need to access multiple URLs from a single instances
|
128
|
-
|
129
|
-
require 'hot_tub'
|
130
|
-
require 'excon'
|
131
|
-
# Our client block must accept the url argument
|
132
|
-
sessions = HotTub::Sessions.new {|url| Excon.new(url) }
|
133
|
-
|
134
|
-
sessions.run("http://somewebservice.com") do |clnt|
|
135
|
-
puts clnt.get(:query => {:some => 'stuff'}).response_header.status
|
136
|
-
end
|
137
|
-
|
138
|
-
sessions.run("https://someotherwebservice.com") do |clnt|
|
139
|
-
puts clnt.get(:query => {:other => 'stuff'}).response_header.status
|
140
|
-
end
|
141
|
-
|
142
130
|
## Dependencies
|
143
131
|
|
144
132
|
* [ThreadSafe](https://github.com/headius/thread_safe)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'benchmark'
|
3
|
+
require 'hot_tub'
|
4
|
+
|
5
|
+
class MocClient
|
6
|
+
def initialize(url=nil,options={})
|
7
|
+
end
|
8
|
+
|
9
|
+
def get
|
10
|
+
sleep(0.01)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
puts `ruby -v`
|
15
|
+
Benchmark.bmbm do |b|
|
16
|
+
|
17
|
+
b.report("single thread") do
|
18
|
+
hot_tub = HotTub::Pool.new(:size => 1, :max_size => 1, :no_reaper => true) { MocClient.new }
|
19
|
+
1000.times.each do
|
20
|
+
hot_tub.run do |conn|
|
21
|
+
conn.get
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
b.report("threaded size 5") do
|
27
|
+
hot_tub = HotTub::Pool.new(:size => 5, :max_size => 5) { MocClient.new }
|
28
|
+
threads = []
|
29
|
+
1000.times.each do
|
30
|
+
threads << Thread.new do
|
31
|
+
hot_tub.run do |conn|
|
32
|
+
conn.get
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
threads.each do |t|
|
37
|
+
t.join
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
b.report("threaded size 5, max 10") do
|
42
|
+
hot_tub = HotTub::Pool.new(:size => 5, :max_size => 10) { MocClient.new }
|
43
|
+
threads = []
|
44
|
+
1000.times.each do
|
45
|
+
threads << Thread.new do
|
46
|
+
hot_tub.run do |conn|
|
47
|
+
conn.get
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
threads.each do |t|
|
52
|
+
t.join
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
b.report("threaded, size 5, no max") do
|
57
|
+
hot_tub = HotTub::Pool.new(:size => 5) { MocClient.new }
|
58
|
+
threads = []
|
59
|
+
1000.times.each do
|
60
|
+
threads << Thread.new do
|
61
|
+
hot_tub.run do |conn|
|
62
|
+
conn.get
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
threads.each do |t|
|
67
|
+
t.join
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]
|
74
|
+
# Rehearsal ------------------------------------------------------------
|
75
|
+
# single thread 0.110000 0.040000 0.150000 ( 10.998645)
|
76
|
+
# threaded size 5 0.250000 0.320000 0.570000 ( 2.490553)
|
77
|
+
# threaded size 5, max 10 0.220000 0.290000 0.510000 ( 1.372965)
|
78
|
+
# threaded, size 5, no max 0.140000 0.130000 0.270000 ( 0.240067)
|
79
|
+
# --------------------------------------------------- total: 1.500000sec
|
80
|
+
|
81
|
+
# user system total real
|
82
|
+
# single thread 0.110000 0.050000 0.160000 ( 11.009793)
|
83
|
+
# threaded size 5 0.320000 0.320000 0.640000 ( 2.559244)
|
84
|
+
# threaded size 5, max 10 0.300000 0.270000 0.570000 ( 1.454562)
|
85
|
+
# threaded, size 5, no max 0.200000 0.130000 0.330000 ( 0.280806)
|
86
|
+
|
87
|
+
|
88
|
+
# rubinius 2.5.8 (2.1.0 bef51ae3 2015-07-14 3.5.1 JI) [x86_64-darwin14.4.0]
|
89
|
+
# Rehearsal ------------------------------------------------------------
|
90
|
+
# single thread 0.182959 0.057407 0.240366 ( 11.014685)
|
91
|
+
# threaded size 5 0.374537 0.274944 0.649481 ( 2.251464)
|
92
|
+
# threaded size 5, max 10 0.216058 0.215593 0.431651 ( 1.137440)
|
93
|
+
# threaded, size 5, no max 0.173595 0.120422 0.294017 ( 0.086586)
|
94
|
+
# --------------------------------------------------- total: 1.615515sec
|
95
|
+
|
96
|
+
# user system total real
|
97
|
+
# single thread 0.177439 0.056605 0.234044 ( 11.031641)
|
98
|
+
# threaded size 5 0.273678 0.302949 0.576627 ( 2.274915)
|
99
|
+
# threaded size 5, max 10 0.212687 0.204235 0.416922 ( 1.130088)
|
100
|
+
# threaded, size 5, no max 0.138528 0.095331 0.233859 ( 0.065469)
|
101
|
+
|
102
|
+
|
103
|
+
# jruby 9.0.3.0 (2.2.2) 2015-10-21 633c9aa Java HotSpot(TM) 64-Bit Server VM 23.5-b02 on 1.7.0_09-b05 +jit [darwin-x86_64]
|
104
|
+
# Rehearsal ------------------------------------------------------------
|
105
|
+
# single thread 1.160000 0.070000 1.230000 ( 11.521177)
|
106
|
+
# threaded size 5 1.280000 0.370000 1.650000 ( 2.450701)
|
107
|
+
# threaded size 5, max 10 0.840000 0.310000 1.150000 ( 1.167782)
|
108
|
+
# threaded, size 5, no max 0.600000 0.160000 0.760000 ( 0.173616)
|
109
|
+
# --------------------------------------------------- total: 4.790000sec
|
110
|
+
|
111
|
+
# user system total real
|
112
|
+
# single thread 0.780000 0.080000 0.860000 ( 11.264802)
|
113
|
+
# threaded size 5 0.560000 0.340000 0.900000 ( 2.269302)
|
114
|
+
# threaded size 5, max 10 0.610000 0.320000 0.930000 ( 1.168468)
|
115
|
+
# threaded, size 5, no max 0.300000 0.150000 0.450000 ( 0.150489)
|
116
|
+
|
data/hot_tub.gemspec
CHANGED
@@ -17,8 +17,10 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_runtime_dependency "thread_safe"
|
18
18
|
|
19
19
|
s.add_development_dependency "rspec"
|
20
|
+
s.add_development_dependency "rspec-autotest"
|
21
|
+
s.add_development_dependency "autotest"
|
20
22
|
s.add_development_dependency "sinatra"
|
21
|
-
s.add_development_dependency "puma", "~> 2.0
|
23
|
+
s.add_development_dependency "puma", "~> 2.0"
|
22
24
|
s.add_development_dependency "excon"
|
23
25
|
|
24
26
|
s.files = `git ls-files`.split("\n")
|
@@ -21,8 +21,9 @@ module HotTub
|
|
21
21
|
action = (@clean_client || known_client_action(clnt,:clean))
|
22
22
|
preform_client_action(clnt,action) if action
|
23
23
|
rescue => e
|
24
|
-
HotTub.logger.error "There was an error cleaning one of your #{self.class.name} clients: #{e}"
|
24
|
+
HotTub.logger.error "There was an error cleaning one of your #{self.class.name} clients: #{e}" if HotTub.logger
|
25
25
|
end
|
26
|
+
clnt
|
26
27
|
end
|
27
28
|
|
28
29
|
# Attempts to close the provided client, checking the options first for a close block
|
@@ -32,8 +33,9 @@ module HotTub
|
|
32
33
|
action = (@close_client || known_client_action(clnt,:close))
|
33
34
|
preform_client_action(clnt,action) if action
|
34
35
|
rescue => e
|
35
|
-
HotTub.logger.error "There was an error closing one of your #{self.class.name} clients: #{e}"
|
36
|
+
HotTub.logger.error "There was an error closing one of your #{self.class.name} clients: #{e}" if HotTub.logger
|
36
37
|
end
|
38
|
+
nil
|
37
39
|
end
|
38
40
|
|
39
41
|
# Attempts to determine if a client should be reaped, block should return a boolean
|
@@ -42,9 +44,9 @@ module HotTub
|
|
42
44
|
action = (@reap_client || known_client_action(clnt,:reap))
|
43
45
|
return preform_client_action(clnt,action) if action
|
44
46
|
rescue => e
|
45
|
-
HotTub.logger.error "There was an error reaping one of your #{self.class.name} clients: #{e}"
|
47
|
+
HotTub.logger.error "There was an error reaping one of your #{self.class.name} clients: #{e}" if HotTub.logger
|
46
48
|
end
|
47
|
-
|
49
|
+
false
|
48
50
|
end
|
49
51
|
|
50
52
|
private
|
data/lib/hot_tub/pool.rb
CHANGED
@@ -61,9 +61,6 @@ module HotTub
|
|
61
61
|
# in seconds. After said time a HotTub::Pool::Timeout exception will be thrown
|
62
62
|
# [:reap_timeout]
|
63
63
|
# Default is 600 seconds. An integer that represents the timeout for reaping the pool in seconds.
|
64
|
-
# [:close_out]
|
65
|
-
# Default is nil. A boolean like value that if it can be interpreted as true force close_client to be called
|
66
|
-
# on checkout clients when #drain! is called
|
67
64
|
# [:close]
|
68
65
|
# Default is nil. Can be a symbol representing an method to call on a client to close the client or a lambda
|
69
66
|
# that accepts the client as a parameter that will close a client. The close option is performed on clients
|
@@ -84,9 +81,8 @@ module HotTub
|
|
84
81
|
raise ArgumentError, 'a block that initializes a new client is required' unless block_given?
|
85
82
|
|
86
83
|
@size = (opts[:size] || 5) # in seconds
|
87
|
-
@wait_timeout = (opts[:wait_timeout] || 10)
|
84
|
+
@wait_timeout = (opts[:wait_timeout] || 10) # in seconds
|
88
85
|
@reap_timeout = (opts[:reap_timeout] || 600) # the interval to reap connections in seconds
|
89
|
-
@close_out = opts[:close_out] # if true on drain! call close_client block on checked out clients
|
90
86
|
@max_size = (opts[:max_size] || 0) # maximum size of pool when non-blocking, 0 means no limit
|
91
87
|
|
92
88
|
@close_client = opts[:close] # => lambda {|clnt| clnt.close} or :close
|
@@ -94,10 +90,10 @@ module HotTub
|
|
94
90
|
@reap_client = opts[:reap] # => lambda {|clnt| clnt.reap?} or :reap? # should return boolean
|
95
91
|
@new_client = new_client
|
96
92
|
|
97
|
-
@
|
98
|
-
@
|
99
|
-
@
|
100
|
-
@
|
93
|
+
@_pool = [] # stores available clients
|
94
|
+
@_pool.taint
|
95
|
+
@_out = [] # stores all checked out clients
|
96
|
+
@_out.taint
|
101
97
|
|
102
98
|
@mutex = Mutex.new
|
103
99
|
@cond = ConditionVariable.new
|
@@ -109,10 +105,10 @@ module HotTub
|
|
109
105
|
end
|
110
106
|
|
111
107
|
# Hand off to client.run
|
112
|
-
def run
|
108
|
+
def run
|
113
109
|
if block_given?
|
114
110
|
clnt = client
|
115
|
-
return
|
111
|
+
return yield clnt if clnt
|
116
112
|
else
|
117
113
|
raise ArgumentError, 'Run requires a block.'
|
118
114
|
end
|
@@ -124,7 +120,7 @@ module HotTub
|
|
124
120
|
# Its possible clients may be returned to the pool after cleaning
|
125
121
|
def clean!
|
126
122
|
@mutex.synchronize do
|
127
|
-
@
|
123
|
+
@_pool.each do |clnt|
|
128
124
|
clean_client(clnt)
|
129
125
|
end
|
130
126
|
end
|
@@ -136,30 +132,54 @@ module HotTub
|
|
136
132
|
# Its possible clients may be returned to the pool after cleaning
|
137
133
|
def drain!
|
138
134
|
@mutex.synchronize do
|
139
|
-
|
140
|
-
|
135
|
+
begin
|
136
|
+
while clnt = (@_pool.pop || @_out.pop)
|
137
|
+
close_client(clnt)
|
138
|
+
end
|
139
|
+
ensure
|
140
|
+
@cond.broadcast
|
141
141
|
end
|
142
|
-
@cond.broadcast
|
143
142
|
end
|
144
143
|
end
|
145
144
|
alias :close! :drain!
|
146
|
-
|
145
|
+
|
146
|
+
# Reset the pool and re-spawn reaper.
|
147
|
+
# or if shutdown allow threads to quickly finish their work
|
148
|
+
# Clients from the previous pool will not return to pool.
|
149
|
+
def reset!
|
150
|
+
@mutex.synchronize do
|
151
|
+
begin
|
152
|
+
while clnt = (@_pool.pop || @_out.pop)
|
153
|
+
close_client(clnt)
|
154
|
+
end
|
155
|
+
if @reaper
|
156
|
+
kill_reaper
|
157
|
+
@reaper = Reaper.spawn(self)
|
158
|
+
end
|
159
|
+
ensure
|
160
|
+
@cond.broadcast
|
161
|
+
end
|
162
|
+
end
|
163
|
+
nil
|
164
|
+
end
|
147
165
|
|
148
166
|
# Kills the reaper and drains the pool.
|
149
167
|
def shutdown!
|
150
168
|
@shutdown = true
|
169
|
+
kill_reaper if @reaper
|
170
|
+
ensure
|
151
171
|
drain!
|
152
172
|
end
|
153
173
|
|
154
174
|
# Remove and close extra clients
|
155
175
|
# Releases mutex each iteration because
|
156
|
-
# reaping is low priority action
|
176
|
+
# reaping is a low priority action
|
157
177
|
def reap!
|
158
|
-
start = Time.now
|
159
178
|
loop do
|
179
|
+
break if @shutdown
|
160
180
|
reaped = nil
|
161
181
|
@mutex.synchronize do
|
162
|
-
reaped = @
|
182
|
+
reaped = @_pool.shift if _reap?
|
163
183
|
end
|
164
184
|
if reaped
|
165
185
|
close_client(reaped)
|
@@ -173,6 +193,12 @@ module HotTub
|
|
173
193
|
(@max_size == 0)
|
174
194
|
end
|
175
195
|
|
196
|
+
def current_size
|
197
|
+
@mutex.synchronize do
|
198
|
+
_total_current_size
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
176
202
|
private
|
177
203
|
|
178
204
|
# Returns an instance of the client for this pool.
|
@@ -190,9 +216,11 @@ module HotTub
|
|
190
216
|
(time <= Time.now)
|
191
217
|
end
|
192
218
|
|
219
|
+
ALARM_MESSAGE = "Could not fetch a free client in time. Consider increasing your pool size."
|
220
|
+
|
193
221
|
def raise_alarm
|
194
|
-
message =
|
195
|
-
HotTub.logger.error message
|
222
|
+
message = ALARM_MESSAGE
|
223
|
+
HotTub.logger.error message if HotTub.logger
|
196
224
|
raise Timeout, message
|
197
225
|
end
|
198
226
|
|
@@ -201,13 +229,18 @@ module HotTub
|
|
201
229
|
def push(clnt)
|
202
230
|
if clnt
|
203
231
|
@mutex.synchronize do
|
204
|
-
|
205
|
-
|
206
|
-
|
232
|
+
begin
|
233
|
+
if @_out.delete(clnt)
|
234
|
+
unless @shutdown
|
235
|
+
@_pool << clnt
|
236
|
+
end
|
237
|
+
end
|
238
|
+
ensure
|
207
239
|
@cond.signal
|
208
240
|
end
|
209
241
|
end
|
210
242
|
close_client(clnt) if @shutdown
|
243
|
+
reap! unless @reaper
|
211
244
|
end
|
212
245
|
nil
|
213
246
|
end
|
@@ -220,8 +253,8 @@ module HotTub
|
|
220
253
|
break if @shutdown
|
221
254
|
raise_alarm if raise_alarm?(alarm)
|
222
255
|
@mutex.synchronize do
|
223
|
-
if (
|
224
|
-
@
|
256
|
+
if clnt = (@_pool.pop || _fetch_new)
|
257
|
+
@_out << clnt
|
225
258
|
else
|
226
259
|
@cond.wait(@mutex,@wait_timeout)
|
227
260
|
end
|
@@ -232,23 +265,11 @@ module HotTub
|
|
232
265
|
|
233
266
|
### START VOLATILE METHODS ###
|
234
267
|
|
235
|
-
# _empty? is volatile; and may cause be inaccurate
|
236
|
-
# if called outside @mutex.synchronize {}
|
237
|
-
def _empty?
|
238
|
-
@pool.empty?
|
239
|
-
end
|
240
|
-
|
241
|
-
# _space? is volatile; and may be inaccurate
|
242
|
-
# if called outside @mutex.synchronize {}
|
243
|
-
def _space?
|
244
|
-
!_empty?
|
245
|
-
end
|
246
|
-
|
247
268
|
# Returns the total number of clients in the pool
|
248
269
|
# and checked out. _total_current_size is volatile and
|
249
270
|
# may be inaccurate if called outside @mutex.synchronize {}
|
250
271
|
def _total_current_size
|
251
|
-
(@
|
272
|
+
(@_pool.length + @_out.length)
|
252
273
|
end
|
253
274
|
|
254
275
|
# Return true if we have reached our limit set by the :size option
|
@@ -265,23 +286,14 @@ module HotTub
|
|
265
286
|
(_total_current_size < @max_size)
|
266
287
|
end
|
267
288
|
|
268
|
-
# We only want to add a client if the pool is empty in keeping with
|
269
|
-
# a lazy model. If the pool is empty we can only add clients if
|
270
|
-
# never_block? is true or there is room to grow. _add? is volatile;
|
271
|
-
# and may be in accurate if called outside @mutex.synchronize {}
|
272
|
-
def _add?
|
273
|
-
(_empty? && (never_block? || _less_than_size?|| _less_than_max?))
|
274
|
-
end
|
275
|
-
|
276
289
|
# Adds a new client to the pool if its allowed
|
277
290
|
# _add is volatile; and may cause threading issues
|
278
291
|
# if called outside @mutex.synchronize {}
|
279
|
-
def
|
280
|
-
return
|
292
|
+
def _fetch_new
|
293
|
+
return nil unless (@_pool.empty? && (never_block? || _less_than_size?|| _less_than_max?))
|
281
294
|
nc = @new_client.call
|
282
|
-
HotTub.logger.info "Adding HotTub client: #{nc.class.name} to pool"
|
283
|
-
|
284
|
-
true
|
295
|
+
HotTub.logger.info "Adding HotTub client: #{nc.class.name} to pool" if HotTub.logger
|
296
|
+
nc
|
285
297
|
end
|
286
298
|
|
287
299
|
# Returns true if we have clients in the pool, the pool
|
@@ -290,15 +302,7 @@ module HotTub
|
|
290
302
|
# volatile; and may be inaccurate if called outside
|
291
303
|
# @mutex.synchronize {}
|
292
304
|
def _reap?
|
293
|
-
(
|
294
|
-
end
|
295
|
-
|
296
|
-
# Returns true if the pool is greater than the :size option and the
|
297
|
-
# pool has been stagnant long enough to allow for reaping (we don't
|
298
|
-
# want to reap under load). _overflow_expired? is volatile; and may
|
299
|
-
# be inaccurate if called outside @mutex.synchronize {}
|
300
|
-
def _overflow?
|
301
|
-
(@pool.length > @size)
|
305
|
+
( !@_pool.empty? && !@shutdown && ((@_pool.length > @size) || reap_client?(@_pool[0])))
|
302
306
|
end
|
303
307
|
|
304
308
|
### END VOLATILE METHODS ###
|
data/lib/hot_tub/reaper.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module HotTub
|
2
|
-
class Reaper
|
2
|
+
class Reaper
|
3
3
|
|
4
4
|
# Creates a new Reaper thread for work.
|
5
5
|
# Expects an object that responses to: :reap!
|
@@ -8,20 +8,21 @@ module HotTub
|
|
8
8
|
# so we rescue, log, and kill the reaper when an exception occurs
|
9
9
|
# https://bugs.ruby-lang.org/issues/6647
|
10
10
|
def self.spawn(obj)
|
11
|
-
th = new {
|
11
|
+
th = Thread.new {
|
12
12
|
loop do
|
13
13
|
begin
|
14
14
|
obj.reap!
|
15
15
|
break if obj.shutdown
|
16
16
|
sleep(obj.reap_timeout || 600)
|
17
17
|
rescue Exception => e
|
18
|
-
HotTub.logger.error "HotTub::Reaper for #{obj.class.name} terminated with exception: #{e.message}"
|
19
|
-
HotTub.logger.error e.backtrace.map {|line| " #{line}"}
|
18
|
+
HotTub.logger.error "HotTub::Reaper for #{obj.class.name} terminated with exception: #{e.message}" if HotTub.logger
|
19
|
+
HotTub.logger.error e.backtrace.map {|line| " #{line}"} if HotTub.logger
|
20
20
|
break
|
21
21
|
end
|
22
22
|
end
|
23
23
|
}
|
24
24
|
th[:name] = "HotTub::Reaper"
|
25
|
+
th.abort_on_exception = true
|
25
26
|
th
|
26
27
|
end
|
27
28
|
|
@@ -30,7 +31,15 @@ module HotTub
|
|
30
31
|
attr_reader :reap_timeout, :reaper, :shutdown
|
31
32
|
|
32
33
|
def reap!
|
33
|
-
raise NoMethodError.new('
|
34
|
+
raise NoMethodError.new('#reap! must be redefined in your class')
|
35
|
+
end
|
36
|
+
|
37
|
+
def kill_reaper
|
38
|
+
if @reaper
|
39
|
+
@reaper.kill
|
40
|
+
@reaper.join
|
41
|
+
@reaper = nil
|
42
|
+
end
|
34
43
|
end
|
35
44
|
end
|
36
45
|
end
|