I call an API to send SMS and save it's response using Redis::throttle to limit the rate of the call to 5 calls every 60s with :
Redis::throttle('throttle:sms')->allow(5)->every(60)->then(function(){
//->API call
//->Save response
},function($error){//could not obtain lock
return $this->release(60);//Put back in queue in 60s
});
I didn't specify any $tries because if the lock cannot be obtain, it count as a try and if I process a long queue and the lock cannot be obtain many time the job will fail without any "real" errors.
But I dont want the job to be processed forever, if there is a real error (like if the response cannot be saved) it should fail without retry especially if the error appends after the API call as a retry will send another SMS (which I definitely don't want).
What I've tried :
Redis::throttle('throttle')->allow(5)->every(60)->then(function(){
try{
$response = MyAPICall();
$test = 8/0;
saveResponse($response);
} catch(\LimiterTimeoutException $e){
throw $e;
} catch(Exception $e){
Log::error($e);
$this->fail($exception = null);
//$this->delete();
}
},function($error){//could not obtain lock
Log::info($error);
return $this->release(60);//Put back in queue in 60s
});
If there is an exception because the lock cannot be obtain, I throw it back to let the queue handle it but if it's another exception, I log the error and fail or delete the job.
But it's not working with either delete() or fail(), the job is always retried.
How can I remove the job if there is an exception other than the LimiterTimeoutException ?
I was missing a "\" before Exception in my catch. Here is the fix code :
Redis::throttle('throttle:sms')->allow(5)->every(60)->then(function(){
$response = myAPICall();
try{
$test = 8/0;
SaveResponse($response);
}
catch (LimiterTimeoutException $exception){
throw $exception;//throw back exception to let redis handle it below
}
catch (\Exception $exception) {
Log::error($exception);
$this->fail($exception);
}
},function($error){//could not obtain lock
return $this->release(60);//Put back in queue in 60s
});
I added $this->fail($exception) to make the job to show up as "failed" in Horizon.
Related
I'm calling a common function from Lumen queue and cron job. During user interaction this function is called from queue ( async purpose ), if something goes wrong with queue execution for example lock wait timeout etc. I have a cron job which is scheduled for every 15 mins to process older transactions. There is no issue when cron is executed but whenever queue is executed MySQL server has gone away error is occurring. Error is occurring at the line DB::connection()->getpdo()->exec('BEGIN');
I did some research on internet, those articles are saying this kind of error will raise when we try to insert large data and we can avoid this error my increasing max_allowed_packet. But error is occurring at beginning line itself, I'm not trying to insert large data ( mostly it should be in KBs ) and moreover same function is working when cron executes for every 15 mins. Following are my code snippets,
public function processTransaction($data)
{
try {
$this->validate($data);
DB::connection()->getpdo()->exec('BEGIN');
// Save & Update DB
DB::connection()->getpdo()->exec('COMMIT');
} catch (Exception $ex) {
DB::connection()->getpdo()->exec('ROLLBACK');
Log::error($ex->getMessage() . '-' . $ex->getTraceAsString());
throw new AppException($ex->getMessage(), $ex->getCode());
}
}
Above is the initial version I tried, in this case error was at ROLLBACK statement. Later I had updated to following
public function processTransaction($data)
{
try {
$this->validate($data);
DB::connection()->getpdo()->exec('BEGIN');
// Save & Update DB
DB::connection()->getpdo()->exec('COMMIT');
} catch (Exception $ex) {
Log::error($ex->getMessage() . '-' . $ex->getTraceAsString());
try {
DB::connection()->getpdo()->exec('ROLLBACK');
} catch (Exception $ex) {
Log::error($ex->getMessage() . '-' . $ex->getTraceAsString());
}
throw new AppException($ex->getMessage(), $ex->getCode());
}
}
Here the error is at BEGIN statement and PDO exception error code is in string, which is also creating the issue for argument 2 AppException (extends Exception class), which excepts argument 2 as integer. I think PDO exception issue can be handled by separately catching PDO exception but I want to know why MySQL server has gone error is getting.
Following is the error logged at BEGIN statement
Error while sending QUERY packet. PID=28968-#0 [internal function]: Laravel\Lumen\Application->Laravel\Lumen\Concerns{closure}(2, 'Error while sen...', '/web-server/...', 164, Array)
Our jobs collect data from external APIs. If one of the jobs errors out because we unexpectedly reach the API daily limit (i.e. HTTP status 429) it is pointless to retry the job again, or even process any similar jobs, till next day.
Is there a way to prevent the current job to be attempted again after a specific event occurs? Ideally I should be able for example to set a flag in the failed job so I can check it on the next attempt (like suggested here)
Edit: I incorrectly referred to the jobs I didn't want to retry as "failed", however I meant if an error (exception) occurs during the API call. I edited the question.
It turns out the solution I was looking for is obvious: just fail() the job:
public function handle()
{
try {
// execute job stuff and throw a custom
// exception if a certain event occurs
} catch (MyCustomException $e) {
$this->fail($e); // it won't be put back in the queue
}
}
In my project i need to do a bulk import and data insertion in the database.
So, I needed to know that when a API request is failed. Here, the problem is that PHP unable to catch that exception because Laravel 5.6 would stop the execution while there is any kind of error.
I needed to stop laravel from automatically stop the execution and let php decide that if an API request failed then lets wait 5 second and try again.
To achieve this i have made a function inside a laravel controller.
private function fetchAPI($id) {
try {
$rawResult = file_get_contents('http://example.com/'.$id.'?key=5453');
} catch (Exception $e) {
sleep(5);
$this->fetchAPI($id);
}
return json_decode($rawResult, true);
}
The above method will utilize the try...catch block. But i have also implemented with a boolean check with no luck:
private function fetchAPI($id) {
$rawResult = file_get_contents('http://example.com/'.$id.'?key=5453');
if($rawResult === FALSE) {
sleep(5);
$this->fetchAPI($id);
} else {
return json_decode($rawResult, true);
}
}
In this scenario how i can re-try if API request failed from a Laravel controller method?
Use \Exception dans not Exception, because Exception is thought as YourCurrentFileNamespace\Exception.
I have a repository that throws an exception if it can't find a record in the database. Rather than redirect to another page I just want to display a warning alert as the record is not critical to the page but is an "exceptional event".
It's probably best to demonstrate with code:
// FxRateRepositoy
public function getRate(/** args **/)
{
$rate = FxRate::where(.... //query to get the rate
if (!rate)
throw new NonExistentCurrencyException(//message);
return $rate;
}
In my start/global.php I have a handler:
App::error(function(NonExistentCurrencyException $e)
{
Session::flash('alert', $e->getMessage());
return \\ ??
});
What to return? I must return a response or the exception continue uncaught. I want to continue to the intended page but with the alert flashed in the session. Is this possible without having to use try catch blocks in every place this method is called?
Ass an additional question, assuming this exception may be thrown multiple times in one request, what's the best way to accumulate alert messages and display them? I'm thinking something akin to the validation messageBag. Can I just use the global $errors variable or should I create a new, specific messagebag for this purpose?
The problem is that if you return nothing from App::error Laravel will display it's default error page. On the other side you can't return a response because you don't know what response it should be in the error handler.
I suggest you handle it in the controller itself.
You can catch the exception there and flash the message or don't throw an exception at all:
$rate = FxRate::where(.... //query to get the rate
if (!rate){
Session::flash('alert', 'Whoops');
}
Also the findOrFail() and firstOrFail methods might be of use. They throw an ModelNotFoundException if the query yields no results:
try {
$rate = FxRate::where(....)->firstOrFail()
// and so on
} catch (Illuminate\Database\Eloquent\ModelNotFoundException $e){
Session::flash('alert', 'Whoops');
}
As for a messages system, take a look at the laracasts/flash package
I can't figure out how I can throw an exception from Guzzle future response handler.
Here's my code:
<?php
require 'vendor/autoload.php';
$client = new \GuzzleHttp\Client();
$req = $client->createRequest('GET', 'http://www.google.com', array(
'future' => true,
));
echo "Sending request\n";
$response = $client->send($req);
try {
$response->then(function ($data) {
echo "Response is received\n";
throw new Exception('Test');
})->then(function () {
// success handler
}, function (Exception $exception) {
echo "Error handler invoked\n";
throw $exception;
});
} catch (Exception $e) {
echo "Exception catched\n";
}
echo "Finish\n";
The catch block is never reached in this case.
You are working with promises when using asynchronous Guzzle requests. Using the then() function off of a FutureResponse will create a promise that is fulfilled or rejected when the request completes. If an error occurs while sending, the promise is rejected, which means the second callback provided to the then function is invoked. When a request completes successfully, it is resolved, and the first callback provided to the then function is invoked. When an exception is thrown in any of the promise functions, the exception is caught inside of the promise and forwarded to the next error handler in the chain. In your example, if the request succeeds, then you throw an exception which will trigger the error callback. Throwing an exception in the error callback will either forward the exception to the next error callback in the promise chain, or silently eat the error (in your case, there are no further error callbacks to trigger).
The React Promises library that is used by Guzzle has more documentation on resolution and rejection forwarding of promises: https://github.com/reactphp/promise#how-promise-forwarding-works. The author of this library is looking into adding a done() function that can be used as a terminal promise handler that actually throws unhandled exceptions.
Asynchronous means that your script will not wait for the response to come back from the server, rather will just send the request and continue executing. In this case, the script reaches its end of life before the response returns, so none of the callbacks are ever executed.
Add this line after the catch to block the script's execution until the response comes back.
$response->getStatusCode();
If you provide more info on what you want to achieve, we might be able to help you further.