rlucas has asked for the wisdom of the Perl Monks concerning the following question:
While implementing a multistep database transaction, I decided to use an idiom I learned in Java: begin transaction , try/catch block, and either commit or rollback based upon the presence of an exception.
In Perl, it was roughly:
eval { $dbh->begin(); # stuff... $mime_entity_instance->send(); } if ($@) { $dbh->rollback(); } else { $dbh->commit(); }
The MIME::Entity instance is a multipart email message, and uses Mail::Mailer to send itself. Unbeknownst to me, Mail::Mailer forks a child process, and when I used taint mode, the PATH was dirty and caused the Mail::Mailer child process exec() to fail. I had a hell of a time figuring out why my code was trying to both commit and rollback.
I've submitted a patch to Mail::Mailer, at http://rt.cpan.org/NoAuth/Bug.html?id=12890
While I can locally solve this in a number of ways, the larger question this brings up is if it is EVER OK to call outside code within an eval block. I can't imagine that everybody, everywhere, checks process IDs before continuing after an eval failure.
You can reproduce the general problem yourself:
#!/usr/bin/perl -w use strict; my $num = '123'; # an external database ID, for example warn "(PID $$) Beginning transaction #$num "; eval { my $pid = fork(); if ($pid) { warn "\t(the parent does OK)"; } else { warn "\t(the child fails)"; die "Child process failure"; } }; if ($@) { warn "(PID $$) Rolling back transaction #$num: died in eval with $ +@"; } else { warn "(PID $$) Committing transaction #$num"; }
Thoughts from the monks? How generally should one deal with this problem?
Update: Grr, I thought my patch had solved the problem -- however, if you are using it in a database environment, the fact that the code exits in the child seems to be calling DESTROY on objects that have external effects (e.g. turning AutoCommit back on in the database). So, problem NOT solved for those of you looking to use Mail::Mailer and derivatives in a transactional eval block.
|
|---|