A client project processes a large volume of data. Freshness is more important than completeness, but avoiding data loss is still important. So is avoiding data corruption; it's better to have to perform a unit of work again than to save incomplete or inaccurate information in the database due to a race condition.
The backend storage mechanism is a relational database with transactions enabled for all write operations. In the (unlikely but possible) case that a transaction fails due to the inability to acquire the correct lock for a unit of work, the code retries the transaction.
Perl's DBI and the KiokuDB persistence layer reveal failed transactions by throwing exceptions. This is all well and good—a transaction error is an exceptional condition that should interrupt normal code flow—except that exceptions as unstructured string data are difficult to use.
It's difficult enough to determine whether an exception occurred from the database or another layer. I had one codepath in which Perl 5 threw a runtime exception due to a missing method (and fortunately testing caught this without data loss, but even so what a hassle).
To my knowledge, there's no easy way to set up an exception handler which catches only exceptions thrown from certain places (much less only exceptions of certain types). In this application, I do very much care about retrying transactions, but if the code has an error I want that exception to propagate to the top level and end the program.
The best I can do at the moment is to perform a regex match against the string of the exception text and hope that testing and careful thought will catch any changes to avoid false negatives and false positives. Granted, the proper place for these errors is most likely in the KiokuDB layer, as committing to use that backend system offers a single point of consistency and abstraction for such details—but that brings up a wider question worth considering in its own post.
I don't know about KiokuDB, but Exception::Class::DBI solves this for DBI:
use Modern::Perl;
use DBI;
use Exception::Class::DBI;
my $dbh = DBI->connect('DBI:mysql:test', 'user', pass, {
PrintError => 0,
RaiseError => 0,
HandleError => Exception::Class::DBI->handler,
});
eval {
$dbh->do('insert into non_extistent_table values(1)')
};
if (my $e = Exception::Class->caught('Exception::Class::DBI')) {
say $e->err;
say $e->errstr;
} else {
# Check for other exceptions as required
}
Gives
1146
Table 'test.non_extistent_table' doesn't exist
I'll try that; I don't know why I forgot it exists. Thanks!