views:

599

answers:

5

I'm playing around with error handling and got a little problem. I connect with a database using the DBI module.

I do my own error handling by using a subroutine that I call upon an error.

I can catch my own dies and handle them just fine but when my database connection fails, the DBI module apparently prints out it's own die :

DBI connect(...) failed: ORA-12154: TNS:could not resolve the connect identifier specified (DBD ERROR: OCIServerAttach) at ...

How would I go about catching this ?

I tried using $SIG{DIE} like so :

local $SIG{__DIE__} = sub {
  my $e = shift;
  print "Error: " .$e;
};

This is on the bottom of my main file, in this file I also call the connect subroutine that is available in a module of my own. I also tried putting this piece of code on the bottom of my module but it still prints the error without the "Error:" in front of it.

A: 

There are lots of switches in DBI, like PrintError, RaiseError, etc. which you can adjust. See http://search.cpan.org/perldoc?DBI

Kinopiko
hmm isn't there a more generic solution ? So that I can catch all other errors with 1 function. I might use a lot of modules that have a lot of ways to output errors, I want to be independent of this ..
Pmarcoen
DBI also has a HandleError method. Or if you set PrintError to false, RaiseError to true, your (Pmarcoen's) error handling should be called.
runrig
A: 

Okay, found the solution, apparently I needed __WARN__ instead of __DIE__ and this piece of code needed to be in the top of the file, before where the error was thrown, unlike the example I read stated :)

Pmarcoen
That's correct, this was a warning and not a death, and the handler must be installed first. You can install it in any module if you wrap it in a `BEGIN{}`.
Ether
read that as well, thank you for verifying.
Pmarcoen
A: 

This is not as generic as an overall die catcher, but specifically for DBI error handling we actually have our own module providing wrappers around database calls; and one of the module's functionalities is to wrap eval (depending on a flag) around each DBI call.

This allows us to do custom error handling on data access level, such as query retries, statistics, automated failover and more - all transparent to the rest of the code.

DVK
A: 

DBI connect(...) failed: ORA-12154: TNS:could not resolve the connect identifier specified (DBD ERROR: OCIServerAttach) at ...

How would I go about catching this ?

To catch and handle this level of error, use eval in block form, "eval { ... }". This will catch any die that happens in the sub code. If the code within an eval block dies, it will set $@ and the block will return false. If the code does not die, $@ will be set to ''.

Using signal handling via SIG{WARN} and SIG{DIE} is troublesome since they are global, there are also race conditions to consider (what happens if I get a signal while I'm handling a different signal? etc. The traditional issues of signal based computing). You're probably writing single-threaded code, so you're not worried about concurrency issues of multiple things calling die, but there is the user to consider (maybe he'll send a SIGKILL while you're trying to open the DBI connection)

In this specific case, you are using DBI. With DBI, you can control what happens in the case of error, if it should die, warn, or fail silently and wait for you to check the return status.

Here is a basic example of using eval { ... }.

my $dbh = eval { DBI->connect( @args) };
if ( $@ )
{
    #DBI->connect threw an error via die
    if ($@ =~ m/ORA-12154/i )
    {
        #handle this error, so I can clean up and continue
    }
    elsif ( $@ =~ m/SOME \s* other \s* ERROR \s+ string/ix )
    {
       #I can't handle this error, but I can translate it
        die "our internal error code #7";
    }
    else 
    {
      die $@; #re-throw the die
    }
}

There are some minor issues with using eval this way, having to do with the global scope of $@. The try::tiny cpan page has a great explanation. Try::Tiny handles a minimal Try/catch block setup and handles localizing $@ and handling the other edge cases.

spazm
This was the way I had it at first, eval however does not catch warnings which I wanted to report as well. Therefor I must use SIG{WARN}.
Pmarcoen
A: 

Include this in your SIG{__DIE__} block:

### Check if exceptions being caught.
return if $^S;

This will prevent your handler from being used on exception-based code that generates a die within an eval block.

Oesor