Project

General

Profile

Actions

Bug #2343

closed

Timeout.timeout doesn't raise Timeout::Error by default

Added by hongli (Hongli Lai) over 14 years ago. Updated almost 13 years ago.

Status:
Rejected
Assignee:
-
Target version:
-
ruby -v:
ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]
Backport:
[ruby-core:26563]

Description

=begin
Timeout.timeout's documentation says that it'll raise Timeout::Error by default, but the code actually raises an object whose class is an anonymous subclass of Timeout::ExitException:

def timeout(sec, klass = nil)
...
exception = klass || Class.new(ExitException)

This is not only inconsistent with the documentation, but also breaks many applications which assume that Timeout::Error is the default.

So which one is wrong? The code or the documentation? If the documentation is wrong then what was the reason for the change?

This issue applies to both of these Ruby versions:

  • ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]
  • ruby 1.9.2dev (2009-08-05 trunk 24397) [i386-darwin9.6.0]
    =end
Actions #1

Updated by nobu (Nobuyoshi Nakada) over 14 years ago

  • Status changed from Open to Rejected

=begin
That anonymous exception is caught in Timeout#timeout, and Timeout::Error is raised again.

$ ./ruby -v -rtimeout -e 'begin; Timeout.timeout(0.1){sleep 1}; rescue Timeout::Error=>e; p e; end'
ruby 1.8.7 (2009-09-11 patchlevel 202) [i686-darwin9.6.0]
#<Timeout::Error: execution expired>

=end

Actions #2

Updated by hongli (Hongli Lai) over 14 years ago

=begin
Your code assumes that Timeout::Error is only caught outside the timeout block. My code fails because it catches the error inside the timeout block:

 require 'timeout'
 Timeout.timeout(1) do
   begin
     sleep
   rescue Timeout::Error
     puts "timed out!"
   end
 end

Is disallowing catching inside the block intentional?
=end

Actions #3

Updated by hongli (Hongli Lai) over 14 years ago

=begin
My code catches inside the timeout block because it needs to perform some cleanup if a timeout occurs. The kind of cleanup that must occur depends on where exactly the timeout happened.
=end

Actions #4

Updated by nobu (Nobuyoshi Nakada) over 14 years ago

=begin
Hi,

At Fri, 6 Nov 2009 23:52:39 +0900,
Hongli Lai wrote in [ruby-core:26565]:

Your code assumes that Timeout::Error is only caught outside
the timeout block. My code fails because it catches the error
inside the timeout block:

Why inside?

require 'timeout'
begin
  Timeout.timeout(1) do
    sleep
  end
rescue Timeout::Error
  puts "timed out!"
end

--
Nobu Nakada

=end

Actions #5

Updated by matz (Yukihiro Matsumoto) over 14 years ago

=begin
Hi,

In message "Re: [ruby-core:26563] [Bug #2343] Timeout.timeout doesn't raise Timeout::Error by default"
on Fri, 6 Nov 2009 21:00:28 +0900, Hongli Lai writes:

|Timeout.timeout's documentation says that it'll raise Timeout::Error by default, but the code actually raises an object whose class is an anonymous subclass of Timeout::ExitException:
|
| def timeout(sec, klass = nil)
| ...
| exception = klass || Class.new(ExitException)
|
|This is not only inconsistent with the documentation, but also breaks many applications which assume that Timeout::Error is the default.
|
|So which one is wrong? The code or the documentation? If the documentation is wrong then what was the reason for the change?

Both are right. It says:

Executes the method's block. If the block execution terminates before

+sec+ seconds has passed, it returns the result value of the block.

If not, it terminates the execution and raises +exception+ (which defaults

to Timeout::Error).

So, it doesn't say anything that timeout would post that exception in
to the block, but timeout itself would raise Timeout::Error. The
reason of this behavior is the subclass of StandardError is too easily
caught by default rescue. If you want to post specific exception into
the block, specify the exception class as a second argument.

I admit the documentation can be improved. Suggestion welcome.

						matz.

=end

Actions #6

Updated by hongli (Hongli Lai) over 14 years ago

=begin

Why inside?

My code performs several things within the timeout block. Depending on where exactly the timeout occurred, it must perform some cleanup before re-throwing the exception. The code looks a little bit like this:


The total time of spawning daemon + waiting for it to become initialized

must take no longer than 5 seconds.

Timeout.timeout(5) do
spawn_daemon_process
wait_for_daemon_to_become_initialized
end

def spawn_daemon_process
begin
pid = ...some code here to spawn a process...
...wait until 'pid' has daemonized into the background...
rescue Timeout::Error
# The daemon failed to daemonize within 5 seconds. Kill it
# before rethrowing Timeout::Error.
Process.kill('KILL', pid)
raise
end
end

Here we don't need to perform any cleanup, so we don't catch Timeout::Error.

def wait_for_daemon_to_become_initialized
done = false
# The daemon is initialized when we can connect to it. Wait
# until that happens, or until we time out
while !done
begin
TCPSocket.new('localhost', 1234)
done = true
rescue SystemCallError
# Daemon not yet initialized.
end
end
end


=end

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0