Can you only catch errors one level deep?

In dbi.rb (from the Ruby DBI package), there is the following code:

# try a quick load and then a caseless scan
begin
  require "#{DBD::DIR}/#{driver_name}/#{driver_name}"
rescue LoadError
  ... || (try other filenames)
end

The "rescue LoadError" is meant to catch an error in case the filename
being loaded didn't exist, in which case it will try to find it
elsewhere.

The problem with "rescue LoadError" is that it will also catch errors
it wasn't meant to! In my case, the file it loaded existed, but
requiring that file threw a LoadError because there was some other
problem with it. So the "rescue" caught an error it wasn't meant to
catch, and just made debugging harder.

This seems to be an inflexibility with the begin/rescue/end construct.
If 'require' was a function that returned a false value upon failing
to load its argument, then the code could have done this:

require "#{DBD::DIR}/#{driver_name}/#{driver_name}" || begin
  ... (try other filenames)
end

Any thoughts about this inflexibility of the begin/rescue/end system?

[previous writing dropped]

The problem with “rescue LoadError” is that it will also catch errors
it wasn’t meant to! In my case, the file it loaded existed, but
requiring that file threw a LoadError because there was some other
problem with it. So the “rescue” caught an error it wasn’t meant to
catch, and just made debugging harder.

What errors did rescue LoadError catch? Where they derived from LoadError?

This seems to be an inflexibility with the begin/rescue/end construct.
If ‘require’ was a function that returned a false value upon failing
to load its argument, then the code could have done this:

Any thoughts about this inflexibility of the begin/rescue/end system?

I’m not sure it’s really an inflexibility of the rescue system. I
think you’re just trying to assign more subtle semantics to the
LoadError exception. I take LoadError to mean that there is an error
preventing the the required code to operate correctly. Maybe
the file doesn’t exist, or there’s a syntax error in the required
code, or you need to load a dependency of the required file first.

If you want better debugging, you could log the details of the
LoadError, even if you have a specific action to recover.

  • alan
···

On Fri, Jul 19, 2002 at 09:04:02AM +0900, Philip Mak wrote:


Alan Chen
Digikata LLC

It caught a LoadError that was thrown by 'require '. However,
the LoadError was thrown not because couldn’t be loaded, but
because had a ‘require’ statement of its own which threw a
LoadError.

I think the problem is that people see code like this:

begin
require 'somefile’
rescue LoadError
# do something
end

and assume that if “# do something” is executed, then it means that
’somefile’ did not exist or isn’t readable.

I’ve also ran into a similar problem here:

begin
someMethod.call(p, *args)
rescue ArgumentError
p.notify "Too many arguments to command"
end

where the code assumed that ArgumentError is thrown by calling
someMethod with too many arguments, but in fact could be thrown by
something inside someMethod.

That’s why I’m thinking that the ability to only catch errors that are
one level deep may be useful.

···

On Fri, Jul 19, 2002 at 09:31:02AM +0900, Alan Chen wrote:

On Fri, Jul 19, 2002 at 09:04:02AM +0900, Philip Mak wrote:

The problem with “rescue LoadError” is that it will also catch errors
it wasn’t meant to! In my case, the file it loaded existed, but
requiring that file threw a LoadError because there was some other
problem with it. So the “rescue” caught an error it wasn’t meant to
catch, and just made debugging harder.

What errors did rescue LoadError catch? Where they derived from LoadError?

Hello –

The problem with “rescue LoadError” is that it will also catch errors
it wasn’t meant to! In my case, the file it loaded existed, but
requiring that file threw a LoadError because there was some other
problem with it. So the “rescue” caught an error it wasn’t meant to
catch, and just made debugging harder.

What errors did rescue LoadError catch? Where they derived from LoadError?

It caught a LoadError that was thrown by 'require '. However,
the LoadError was thrown not because couldn’t be loaded, but
because had a ‘require’ statement of its own which threw a
LoadError.

I think the problem is that people see code like this:

begin
require 'somefile’
rescue LoadError

do something

end

and assume that if “# do something” is executed, then it means that
’somefile’ did not exist or isn’t readable.

I guess you could examine the backtrace:

rescue LoadError => e
b = e.backtrace

now look at the size of b …

I’m sort of winging it here – not sure if there are conditions under
which this would or wouldn’t work.

David

···

On Fri, 19 Jul 2002, Philip Mak wrote:

On Fri, Jul 19, 2002 at 09:31:02AM +0900, Alan Chen wrote:

On Fri, Jul 19, 2002 at 09:04:02AM +0900, Philip Mak wrote:


David Alan Black
home: dblack@candle.superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

Philip Mak pmak@animeglobe.com writes:

I think the problem is that people see code like this:

begin
require 'somefile’
rescue LoadError

do something

end

and assume that if “# do something” is executed, then it means that
’somefile’ did not exist or isn’t readable.

I’m not sure that’s a justified assumption – all you really know is
that there was a problem loading the file.

You could always test separately to see if the file existed and was
readable.

Dave

That’s why I’m thinking that the ability to only catch errors that are
one level deep may be useful.

i would agree. the class structure should get very fine grained, almost
to the point of the individual error messages. note, that’s probably a
good bit of work, but it would be more flexible that way.

~trasnsami

···

On Thu, 2002-07-18 at 19:01, Philip Mak wrote:

On Fri, Jul 19, 2002 at 09:31:02AM +0900, Alan Chen wrote:

On Fri, Jul 19, 2002 at 09:04:02AM +0900, Philip Mak wrote:

The problem with “rescue LoadError” is that it will also catch errors
it wasn’t meant to! In my case, the file it loaded existed, but
requiring that file threw a LoadError because there was some other
problem with it. So the “rescue” caught an error it wasn’t meant to
catch, and just made debugging harder.

What errors did rescue LoadError catch? Where they derived from LoadError?

It caught a LoadError that was thrown by 'require '. However,
the LoadError was thrown not because couldn’t be loaded, but
because had a ‘require’ statement of its own which threw a
LoadError.

I think the problem is that people see code like this:

begin
require 'somefile’
rescue LoadError

do something

end

and assume that if “# do something” is executed, then it means that
’somefile’ did not exist or isn’t readable.

I’ve also ran into a similar problem here:

begin
someMethod.call(p, *args)
rescue ArgumentError
p.notify "Too many arguments to command"
end

where the code assumed that ArgumentError is thrown by calling
someMethod with too many arguments, but in fact could be thrown by
something inside someMethod.


~transami

(") dobee dobee do…
\v/
^ ^

I’m still not sure what this gets you. In production code, if you’re
doing extra work to only error correct one level deep, you still have
an error which will cause the program to crash with a backtrace –
arguably the same backtrace it would have crashed with had you not
limited your rescue one level deep. If you’re debugging it’s more
useful, buy I think that having asserts and logging setup against
all rescues might be a better approach, just to get an idea of your code is
tripping any rescue clauses frequently.

Hmm, is there a way to hook into the rescue clauses to automatically
log all rescue events? A callback might be nice. Maybe I’ll poke
around and see if anything interesting occurs to me.

···

On Fri, Jul 19, 2002 at 10:01:29AM +0900, Philip Mak wrote:

That’s why I’m thinking that the ability to only catch errors that are
one level deep may be useful.


Alan Chen
Digikata LLC

I guess you could examine the backtrace:

rescue LoadError => e
b = e.backtrace

now look at the size of b …

I’m sort of winging it here – not sure if there are conditions under
which this would or wouldn’t work.

Some quick empirical study suggests that if the immediate require fails, the
backtrace is of size 2, the first item giving
filename:line_number:message,
the second giving
filename:line number

If the required file itself has a require problem, then backtrace is of size
4.

e.g., where faketest.rb is the main script, requiring ‘fake1’ (which in turn
requires a nonexistent file, the root cause of the error)

begin
require 'fake1’
rescue LoadError
puts $!.backtrace.size
print $!.backtrace.each_index{ |x| puts “backtrace #{x} =
#{$!.backtrace[x]}”}
end

prints

4
backtrace 0 = ./fake1.rb:1:in require' backtrace 1 = ./fake1.rb:1 backtrace 2 = faketest.rb:2:inrequire’
backtrace 3 = faketest.rb:2

James

···

David


David Alan Black
home: dblack@candle.superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

The backtrace is not the same. I was installing DBI with Pgsql today,
and I got this error when I tried to do DBI.connect:

$ ruby -e"require ‘dbi’; dbh = DBI.connect(‘dbi:Pg:seasft’)"
here
/usr/local/lib/ruby/site_ruby/1.6/dbi/dbi.rb:435:in load_driver': Could not load driver (uninitialized constant Pg at DBI::DBD) (DBI::InterfaceError) from /usr/local/lib/ruby/site_ruby/1.6/dbi/dbi.rb:349:in_get_full_driver’
from /usr/local/lib/ruby/site_ruby/1.6/dbi/dbi.rb:329:in `connect’
from -e:1

That error message was not meaningful at all. I had to look at
dbi.rb’s code to see what was going on. I finally decided that the
error was being caused when dbi.rb did “require ‘DBD/Pg/Pg’”. So then
I typed this:

$ ruby -e"require ‘DBD/Pg/Pg’"
/usr/local/lib/ruby/site_ruby/1.6/DBD/Pg/Pg.rb:33:in require': libpq.so.2: cannot open shared object file: No such file or directory - /usr/local/lib/ruby/1.6/i686-linux/postgres.so (LoadError) from /usr/local/lib/ruby/site_ruby/1.6/DBD/Pg/Pg.rb:33 from -e:1:inrequire’
from -e:1

which is what the real problem was (I didn’t have libpq.so.2 in my
library path). But, because dbi.rb’s begin/rescue caught an error it
wasn’t meant to catch, the real error message got obscured. If it
hadn’t, I would have been able to spot the error right away instead of
digging into the source code of a library (which I shouldn’t have to
do).

···

On Fri, Jul 19, 2002 at 02:41:21PM +0900, Alan Chen wrote:

On Fri, Jul 19, 2002 at 10:01:29AM +0900, Philip Mak wrote:

That’s why I’m thinking that the ability to only catch errors that are
one level deep may be useful.

I’m still not sure what this gets you. In production code, if you’re
doing extra work to only error correct one level deep, you still have
an error which will cause the program to crash with a backtrace –
arguably the same backtrace it would have crashed with had you not
limited your rescue one level deep.

Tom Sawyer wrote:

···

On Thu, 2002-07-18 at 19:01, Philip Mak wrote:

That’s why I’m thinking that the ability to only catch errors that are
one level deep may be useful.

i would agree. the class structure should get very fine grained, almost
to the point of the individual error messages. note, that’s probably a
good bit of work, but it would be more flexible that way.

Something like what Python does?


Giuseppe “Oblomov” Bilotta

Axiom I of the Giuseppe Bilotta
theory of IT:
Anything is better than MS