I've got an elaborate script that relies on exception throwing/catching to deal with errors. Yesterday, I tried to run it on a newly allocated virtual server and it ran fine as it was looping, waiting for a batch of data to arrive. When the batch of data became available, it forked off processes to handle the data and each process died quietly except those child processes had exited with an error condition. The code looks something like this:
$res = pcntl_wait($status); // wait until a child ends up
MTLog::getInstance()->debug2('$res = pcntl_wait($status); called with $res = '.$res);
if ( $res>0 ) {
// this is fine!
MTLog::getInstance()->debug('Finishing child with pid '.$res);
} else {
// this is bad and was the result i was getting
$error_msg = 'Outch1, this should not happen. Verify your implementation.';
$error_msg .= "\n" . $this->getWaitStatusInfo($status) . "\n";
$error_msg .= "Outch1 recorded in file " . __FILE__ . " at line " . __LINE__ . "\npcntl_wait returned " . print_r($res, TRUE) . "\n" . $this->reportErrors();
MTLog::getInstance()->error($error_msg);
// added by jta to optionally send a notification
$this->tryNotification($error_msg);
$this->run = false;
break;
}
As you can see I've take pains to write a log when this happens and send myself an email notification. The problem is that the error message I received was not helpful in finding the problem:
2013-06-13 00:09:03 [18260] ERROR : Outch1, this should not happen. Verify your implementation.STATUS: 65280
pcntl_wifexited: TRUE
pcntl_wexitstatus: 255
pcntl_wifsignaled: FALSE
Outch1 recorded in file /root/ImageDaemon/class.MTDaemon.php at line 404
pcntl_wait returned -1
error_get_last did not return any errors
There was no remaining evidence of the fatal errors that ended my child processes available at the point where I realize there has been an error. After some time-consuming digging, I realized that this server's PHP installation doesn't have the curl extension. The result was that the code tried to call a function which didn't exist resulting in a fatal E_ERROR being thrown and the script dying/exiting with an error condition. The "PHP Fatal error: Call to undefined function curl_init() in /foo/bar/file.php on line XX" error message that would have been most helpful in finding the problem was lost forever for a variety of reasons:
1) php's error_log setting is empty meaning that error messages go to STDERR for scripts invoked via CLI
2) The process that encountered the fatal error was forked off from a process that was itself forked off from the actual cli script I had evoked so whatever got sent to STDERR never actually bubbled up to a terminal window or output file so that I could see it
3) my code relies heavily on exception handling but so far I haven't found a way to catch an E_ERROR. According to the docs on [man]set_error_handler[/man], you can't handle these with your own error function:
The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.
I nevertheless tried to make a function which turned errors into thrown exceptions but it doesn't help when you try to call a function that doesn't exist:
<?php
class foo {
public function __construct() {
set_error_handler(array($this, "error_handler"));
try {
echo "here i am\n";
this_function_does_not_exist();
trigger_error("a user error", E_USER_ERROR);
echo "made it to the end\n";
} catch (Exception $e) {
die("exception caught:" . $e->getMessage() . "\n");
}
echo "function foo completed\n";
}
public static function error_handler($errno, $errstr, $errfile=NULL, $errline=NULL, $errcontext=NULL) {
$msg = " handled by my error handler in file " . $errfile . " at line " . $errline . "\n===\n" . $errstr . "\n===\n";
switch ($errno) {
case E_ERROR:
$msg = "E_ERROR: " . $msg;
die("fatal error\n");
break;
case E_USER_ERROR:
$msg = "E_USER_ERROR: " . $msg;
break;
case E_WARNING:
$msg = "E_WARNING: " . $msg;
break;
case E_NOTICE:
$msg = "E_NOTICE: " . $msg;
break;
case E_STRICT:
$msg = "E_STRICT: " . $msg;
break;
default:
$msg = "unrecognized errno ($errno): " . $msg;
}
throw new Exception("Exception " . $msg);
}
} // class foo
$v = new foo();
?>
Is there some way to turn a fatal E_ERROR into an exception instead?