44
55class TestTimeout < Test ::Unit ::TestCase
66
7+ private def kill_timeout_thread
8+ thread = Timeout . const_get ( :State ) . instance . instance_variable_get ( :@timeout_thread )
9+ thread . kill
10+ thread . join
11+ end
12+
713 def test_public_methods
814 assert_equal [ :timeout ] , Timeout . private_instance_methods ( false )
915 assert_equal [ ] , Timeout . public_instance_methods ( false )
@@ -221,6 +227,24 @@ def o.each
221227 end
222228 end
223229
230+ def test_handle_interrupt_with_exception_class
231+ bug11344 = '[ruby-dev:49179] [Bug #11344]'
232+ ok = false
233+ assert_raise ( Timeout ::Error ) {
234+ Thread . handle_interrupt ( Timeout ::Error => :never ) {
235+ Timeout . timeout ( 0.01 , Timeout ::Error ) {
236+ sleep 0.2
237+ ok = true
238+ Thread . handle_interrupt ( Timeout ::Error => :on_blocking ) {
239+ sleep 0.2
240+ raise "unreachable"
241+ }
242+ }
243+ }
244+ }
245+ assert ( ok , bug11344 )
246+ end
247+
224248 def test_handle_interrupt
225249 bug11344 = '[ruby-dev:49179] [Bug #11344]'
226250 ok = false
@@ -231,13 +255,102 @@ def test_handle_interrupt
231255 ok = true
232256 Thread . handle_interrupt ( Timeout ::ExitException => :on_blocking ) {
233257 sleep 0.2
258+ raise "unreachable"
234259 }
235260 }
236261 }
237262 }
238263 assert ( ok , bug11344 )
239264 end
240265
266+ def test_handle_interrupt_with_interrupt_mask_inheritance
267+ issue = 'https://github.com/ruby/timeout/issues/41'
268+
269+ [
270+ -> { } , # not blocking so no opportunity to interrupt
271+ -> { sleep 5 }
272+ ] . each_with_index do |body , idx |
273+ # We need to create a new Timeout thread
274+ kill_timeout_thread
275+
276+ # Create the timeout thread under a handle_interrupt(:never)
277+ # due to the interrupt mask being inherited
278+ Thread . handle_interrupt ( Object => :never ) {
279+ assert_equal :ok , Timeout . timeout ( 1 ) { :ok }
280+ }
281+
282+ # Ensure a simple timeout works and the interrupt mask was not inherited
283+ assert_raise ( Timeout ::Error ) {
284+ Timeout . timeout ( 0.001 ) { sleep 1 }
285+ }
286+
287+ r = [ ]
288+ # This raises Timeout::ExitException and not Timeout::Error for the non-blocking body
289+ # because of the handle_interrupt(:never) which delays raising Timeout::ExitException
290+ # on the main thread until getting outside of that handle_interrupt(:never) call.
291+ # For this reason we document handle_interrupt(Timeout::ExitException) should not be used.
292+ exc = idx == 0 ? Timeout ::ExitException : Timeout ::Error
293+ assert_raise ( exc ) {
294+ Thread . handle_interrupt ( Timeout ::ExitException => :never ) {
295+ Timeout . timeout ( 0.1 ) do
296+ sleep 0.2
297+ r << :sleep_before_done
298+ Thread . handle_interrupt ( Timeout ::ExitException => :on_blocking ) {
299+ r << :body
300+ body . call
301+ }
302+ ensure
303+ sleep 0.2
304+ r << :ensure_sleep_done
305+ end
306+ }
307+ }
308+ assert_equal ( [ :sleep_before_done , :body , :ensure_sleep_done ] , r , issue )
309+ end
310+ end
311+
312+ # Same as above but with an exception class
313+ def test_handle_interrupt_with_interrupt_mask_inheritance_with_exception_class
314+ issue = 'https://github.com/ruby/timeout/issues/41'
315+
316+ [
317+ -> { } , # not blocking so no opportunity to interrupt
318+ -> { sleep 5 }
319+ ] . each do |body |
320+ # We need to create a new Timeout thread
321+ kill_timeout_thread
322+
323+ # Create the timeout thread under a handle_interrupt(:never)
324+ # due to the interrupt mask being inherited
325+ Thread . handle_interrupt ( Object => :never ) {
326+ assert_equal :ok , Timeout . timeout ( 1 ) { :ok }
327+ }
328+
329+ # Ensure a simple timeout works and the interrupt mask was not inherited
330+ assert_raise ( Timeout ::Error ) {
331+ Timeout . timeout ( 0.001 ) { sleep 1 }
332+ }
333+
334+ r = [ ]
335+ assert_raise ( Timeout ::Error ) {
336+ Thread . handle_interrupt ( Timeout ::Error => :never ) {
337+ Timeout . timeout ( 0.1 , Timeout ::Error ) do
338+ sleep 0.2
339+ r << :sleep_before_done
340+ Thread . handle_interrupt ( Timeout ::Error => :on_blocking ) {
341+ r << :body
342+ body . call
343+ }
344+ ensure
345+ sleep 0.2
346+ r << :ensure_sleep_done
347+ end
348+ }
349+ }
350+ assert_equal ( [ :sleep_before_done , :body , :ensure_sleep_done ] , r , issue )
351+ end
352+ end
353+
241354 def test_fork
242355 omit 'fork not supported' unless Process . respond_to? ( :fork )
243356 r , w = IO . pipe
0 commit comments