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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YThjNDQ4Y2Q0YTg2NGNmY2RhMTVhZjg1NzNiMmM1NjZjNmE2NDUyNw==
5
- data.tar.gz: !binary |-
6
- ZjAwZWMwNWJkNzE4ZDhlYTg2MzIyYjdlMGM1YTIxY2JkZWQ1ZGY3ZA==
2
+ SHA1:
3
+ metadata.gz: 814f65fdb0c6f97026ed4dcabbe50d8deaab6a4c
4
+ data.tar.gz: 4c9744264dae35c80d089194de51e8c445193cdd
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- YjIyMTkxZDZkNDc2Nzc2OGVlMGI5YTgzODAxZWYwZGIzOWIxNzRkOGQ5NTM4
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
@@ -1,13 +1,10 @@
1
+ sudo: false
2
+ cache: bundler
1
3
  language: ruby
2
4
  rvm:
3
- - 2.1.1
4
- - 2.1.0
5
+ - 2.2
6
+ - 2.1
5
7
  - 2.0.0
6
- - 1.9.3
7
- - rbx-2
8
- - rbx
9
8
  - ruby-head
10
- - jruby-head
11
- matrix:
12
- allow_failures:
13
- - rvm: rbx
9
+ - rbx-2
10
+ - jruby
data/HISTORY.md CHANGED
@@ -4,7 +4,14 @@ HotTub Changelog
4
4
  Head
5
5
  =======
6
6
 
7
- - Nothing yet
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2014 Joshua T. Mckinney
1
+ Copyright (c) 2010-2015 Joshua T. Mckinney
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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 pool or client instance.
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.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
- return false
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) # in seconds
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
- @pool = [] # stores available clients
98
- @pool.taint
99
- @out = [] # stores all checked out clients
100
- @out.taint
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(&block)
108
+ def run
113
109
  if block_given?
114
110
  clnt = client
115
- return block.call(clnt) if clnt
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
- @pool.each do |clnt|
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
- while clnt = (@pool.pop || (@close_out && @out.pop))
140
- close_client(clnt)
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
- alias :close_all! :drain!
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 = @pool.shift if _reap?
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 = "Could not fetch a free client in time. Consider increasing your pool size."
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
- @out.delete(clnt)
205
- unless @shutdown
206
- @pool << clnt
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 (_space? || _add)
224
- @out << clnt = @pool.pop
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
- (@pool.length + @out.length)
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 _add
280
- return false unless _add?
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
- @pool << nc
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
- (_space? && !@shutdown && (_overflow? || reap_client?(@pool[0])))
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 ###
@@ -1,5 +1,5 @@
1
1
  module HotTub
2
- class Reaper < Thread
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(':reap! must be redefined in your class')
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