Retry a try/catch when it fails - php

I've tried a few different try/catch loops to try and solve problems automatically but they always seem to cause the software to die.
$doLoop = true;
while ($doLoop) {
try {
//do a thing
insertIntoDb($data);
} catch (Exception $e) {
//try again
insertIntoDb($data);
//write error to file
writeError($e);
}
}
Here's my original try/catch.
The issue is sometimes the MySQL server 'goes away' and I need to catch that exception and keep retrying until it comes back.
What can I change here to get this to keep retrying until successful?

use a break as last statement in your try block to leave the loop only on success:
while (true) {
try {
// do something
insertIntoDb($data);
break;
}
catch (Exception $e) {
writeError($e);
}
// sleep 200ms to give the MySQL server time to come back up
usleep(200000);
}
But you should also limit the number of retries by using a for loop instead.
Otherwise, your code might run in an infinite loop:
// limit the number of retries
for ($i=1; $i <= 3; $i++) {
try {
// do something
insertIntoDb($data);
break;
}
catch (Exception $e) {
writeError($e);
}
// sleep 200ms to give the MySQL server time to come back up
usleep(200000);
}
Also note the usleep() call:
This is important because otherwise the PHP process would take all resources (100% CPU) while retrying as fast as possible. You can adjust the value to fit your needs. (maybe 200ms is too long or too short in your case)
Also note that you may need to reconnect to the MySQL DB on failure! I did not include code for that case in my example.

This will only work, if your function insertIntoDb will throw an Exception. If you use mysql* functions, then it won't work.
After insertIntoDb($data); you should set $doLoop = false

Related

PHP Generator: catch exception and yield

Is it possible to catch an exception in an generator and just yield the next value? I tried something similar like the code in the example below, but it stops on an exception and does not yield the next value as "expected".
function generator(){
foreach($aLotOfWork as $task){
try {
$promise = doSomethingThatCanFailBadly($task);
yield $promise;
} catch (Exception $e) {
echo "oh.. there is an error, but I don't care and continue";
}
}
}
IMHO This is not a duplicate of (php: catch exception and continue execution, is it possible?), because this person just wanted to know how to catch an exception in php and go on. In my case I already catch all exceptions, but the generator stops and does not go on as intended.
Your code is right and it will capture the exception and continue, check this out:
$ cat so.php
<?php
function doSomethingThatCanFailBadly($task) {
if ($task == 3) {
throw new Exception();
}
return $task;
}
function generator(){
$aLotOfWork = array(1,2,3,4,5);
foreach($aLotOfWork as $task){
try {
$promise = doSomethingThatCanFailBadly($task);
yield $promise;
} catch (Exception $e) {
echo "oh.. there is an error, but I don't care and continue\n";
}
}
}
foreach (generator() as $number) {
echo "$number\n";
}
?>
$ php so.php
1
2
oh.. there is an error, but I don't care and continue
4
5
Have a look at your error stack trace. Maybe what's happening is that something inside your doSomethingThatCanFailBadly method is producing an exception but it is also catching it and forcing the quit with die() or exit() before it ever gets to your catch block. There's not much you can do in that case. You could maybe use register_shutdown_function and see if that helps, but that is starting too look messy.
You would need to save to output of the generator to an array, and if during individual yields an exception is thrown, with right exception handling, it simply will not get into the array.
In your example the doSomethingThatCanFailBadly throws an exception, then the program flow gets to the catch branch. So actually doSomethingThatCanFailBadly has no return value that could be assigned to $promise - so there's nothing to yield at that point.
#palako explained the problem very nicely, but he does not really provide a solution for my case. I use this generator to generate Promises and the consumer (Guzzle's EachPromise-method) seems to stop working on an Exception. So I replaced the throw new SomeException('error message') statement by return RejectedPromise('error message').
I have to admit that this is a very situation-specific solution, but you can do something similar in other settings too: just return an object instead of using exceptions.

is this an endless loop because of the exception?

I have the following code:
do {
try {
$result = $class->function_x($params); //this will fire an Exception
break;
}
catch (Exception $e)
{
error_log($e->getMessage());
sleep(5);
}
} while(true);
Is this an endless loop?
The method call fire an Exception so the catch write to error_log and wait for 5 seconds, but because of the Exception the break is not executed (to break the while) and the loop will be endless.
Is that correct or I am missing something?
If $result = $class->function_x($params); throws Exception it will never go to break;, because it will jump out to catch just after an Exception.
So yes, it looks like endless loop.
Yes, if $class->function_x($params); always throws exception, then it is endless loop.
As soon as the exception thrown, the control is given into catch block and break will never be executed.
You should not use break statement inside a try catch. You can use goto for this task. This will prevent your exceptions :
do {
try {
$result = $class->function_x($params);
if(something) goto bre;
} catch(Exception $e) {
error_log($e->getMessage());
sleep(5);
}
bre:
} while(true);

Laravel DB::transaction() return value

It's my first time to use DB::transaction() but how exactly does it work if a transaction fails or is successful? In the example below, do I have to manually assign a value to return true, or if it fails will the method either return false or totally exit the transaction (therefore skipping the rest of the code)? The docs aren't so helpful on this.
use Exception;
use DB;
try {
$success = DB::transaction(function() {
// Run some queries
});
print_r($success);
} catch(Exception $e) {
echo 'Uh oh.';
}
Solution
I wrote down this solution for others who might be wondering.
Since I was more concerned about returning a boolean value depending on the success of my query, with a few modifications it now returns true/false depending on its success:
use Exception;
use DB;
try {
$exception = DB::transaction(function() {
// Run queries here
});
return is_null($exception) ? true : $exception;
} catch(Exception $e) {
return false;
}
Take note that the variable $exception is never returned since if something goes wrong with your query, the catch is immediately triggered returning false. Thanks to #ilaijin for showing that an Exception object is thrown if something goes wrong.
By giving a look at function transaction it does its process inside a try/catch block
public function transaction(Closure $callback)
{
$this->beginTransaction();
// We'll simply execute the given callback within a try / catch block
// and if we catch any exception we can rollback the transaction
// so that none of the changes are persisted to the database.
try
{
$result = $callback($this);
$this->commit();
}
// If we catch an exception, we will roll back so nothing gets messed
// up in the database. Then we'll re-throw the exception so it can
// be handled how the developer sees fit for their applications.
catch (\Exception $e)
{
$this->rollBack();
throw $e;
}
So throws an Exception (after the rollback) if fails or returns $result, which is the result of your callback
There is a short version if you want to use the default transaction method that ships with Laravel without handling it manually.
$result = DB::transaction(function () {
// logic here
return $somethingYouWantToCheckLater;
});
You can also use the following
DB::rollback();

Can I use try-catch-finally like this?

I'm using try-catch for years, but I never learned how and when to use finally, because I never understood the point of finally (I've read bad books)?
I want to ask you about use of finally in my case.
My code example should explain everything:
$s = "";
$c = MyClassForFileHandling::getInstance();
try
{
$s = $c->get_file_content($path);
}
catch FileNotFoundExeption
{
$c->create_file($path, "text for new file");
}
finally
{
$s = $c->get_file_content($path);
}
Is this correct use of finally?
More precise question:
Shall I use finally (in future PHP versions or other languages) for handling "create something if it not exists" operations?
Finally will always be executed, so in this case, it is not its intended purpose, since normal execution would reopen the file a second time. What you intend to do would be achieved in the same (cleaner) way if you do
$s = "";
$c = MyClassForFileHandling::getInstance();
try
{
$s = $c->get_file_content($path);
}
catch(FileNotFoundExeption $e)
{
$c->create_file($path, "text for new file");
$s = $c->get_file_content($path);
}
Then the manual says:
For the benefit of someone anyone who hasn't come across finally blocks before, the key difference between them and normal code following a try/catch block is that they will be executed even the try/catch block would return control to the calling function.
It might do this if:
code if your try block contains an exception type that you don't catch
you throw another exception in your catch block
your try or catch block calls return
Finally would then be useful in this kind of scenario:
function my_get_file_content($path)
{
try
{
return $c->get_file_content($path);
}
catch(FileNotFoundExeption $e)
{
$c->create_file($path, "text for new file");
return $c->get_file_content($path);
}
finally
{
$c->close_file_handler();
}
}
=> if you need to make sure you close your file handler in this case, or some resource in general.
finally wasn't introduced into PHP until version 5.5 which has not been released yet so that why you haven't seen any examples with it yet. So unless you're running and alpha version of PHP 5.5 you can't use finally yet.
From the manual (exceptions)
In PHP 5.5 and later, a finally block may also be specified after the catch blocks. Code within the finally block will always be executed after the try and catch blocks, regardless of whether an exception has been thrown, and before normal execution resumes.
Example from the manual of using finally
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
else return 1/$x;
}
try {
echo inverse(5) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "First finally.\n";
}
try {
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "Second finally.\n";
}
// Continue execution
echo 'Hello World';
?>
Finally means what do you want to DO Finally.
try
{
$s = $c->get_file_content($path);
}
catch FileNotFoundExeption
{
$c->create_file($path, "text for new file");
}
finally
{
//Create a pdf with my file
//or, Rename my file
//or, store my file into Database
}
No matter what happens(regardless of whether an exception has been thrown) inside try or catch, 'Finally code' will execute.
So, no point of using same code over 'try' and 'finally'.
Does this simply answer your question?
I just want to appoint that if an Exception occurs in the try block, the exception will be correctly raised even if the finally block is present.
The usefulness of the finally block is for clean and free resources.
I think it's best use is when, for example, you upload a file but then an error happens:
$tmp_name = null;
try {
$tmp_name = tempnam(UPLOAD_DIR, 'prefix');
move_uploaded_file($file['tmp_name'], $tmp_name);
ImageManager::resize($tmp_name, $real_path, $width, $height); // this will rise some exception
}
finally {
if($tmp_name)
unlink($tmp_name); // this will ensure the temp file is ALWAYS deleted
}
As you can see, in this way no matter what happen, the temp file will be correctly deleted.
If we would emulate the finally clause in older version of PHP, we should write something like this:
// start finally
catch(Exception $ex) {
}
if($tmp_name)
unlink($tmp_name);
if( isset($ex) )
throw $ex;
// end finally
Note that the exception has been re-thrown in case the catch block caught something. It isn't clear as the finally version, but works the same.

Php for loop with try catch

This question is in continuation from this as suggested by one of the user.
I am using the getIDs function as below to process the id's. CheckValid() will check if the id's is a valid one to be processed, if yes then it will go to the next one updateUsers(). Check valid just checks for a condition and if not it throws an exception. updateUsers() just updates a column if it passes checkValid().
Problem – If I get 4 id's as output from getIDs(), and with the execute(), it process 2 for example and if it fails for 2nd id, it doesn't continue for the rest 2 id's ..I want it to continue so I commented out the "throw $e in the catch block".
Function execute() {
for($i=0 ; $i<count($this->getIDs()); $i++) {
try {
$this->checkValid();
$this->updateUsers();
} catch(Exception $e) {
//throw $e;
}
have you try a simple continue in the catch block ? didn't test but maybe something like that:
Function execute() {
for($i=0 ; $i<count($this->getIDs()); $i++) {
try {
$this->checkValid();
$this->updateUsers();
} catch(Exception $e) {
//throw $e;
continue; // if not working try a continue 2;
}
}
}
It sounds like you're using exceptions as booleans, I'd suggest avoiding that, as it gets confusing quickly, unless you really need the contents of the exception. See if this makes any sense for your use case (I'll grant, it may not).
// returns true if valid, false otherwise
function checkValid(){
try {
// do your validation
return true;
} catch (Exception $e) {
// optional: save the exception in case we want to know about it
$this->last_error = $e;
return false;
}
}
function execute() {
for($i=0 ; $i<count($this->getIDs()); $i++) {
if($this->checkValid()){
$this->updateUsers();
}
// if you want to do something with an error, simply add an else clause
// and handle $this->last_error
}
}
Also, I obviously don't know your code or what exactly you're doing, but looping n times and calling checkValid() and updateUsers() without parameters seems like very poor practice. Much better to, for instance, loop over the list of IDs and check each ID and user in turn, something like this:
foreach($this->getIDs() as $id){
if($this->checkValid($id)){
$this->updateUser($id);
} else {
// an advantage of this is now we can know exactly which ID failed,
// because we have the $id variable
}
}

Categories