Stop queue on error - laravel

I am trying to delete my queue on error.
I have tried the following:
file: global.php
Queue::failing(function($connection, $job, $data)
{
// Delete the job
$job->delete();
});
But when my queue fails like this one does:
public function fire($job, $data){
undefined_function(); // this function is not defined and will trow a error
}
Then the job is not deleted for some reasons.
Any ideas?

From their user manual, section Checking The Number Of Run Attempts:
If an exception occurs while the job is being processed, it will automatically be released back onto the queue.
I think you need to set the maximum number of tries to 1, so the job will fail on the first error.
php artisan queue:listen --tries=1

you can prevent your job from throwing exception
public function handle()
{
try {
//your code here
}catch (\Exception $e){
return true;
}
}

Related

How to run a code when a Laravel Job try is killed by timeout (Horizon)

I created a Laravel Job with 3 tries and timeout after 10 minutes. I am using Horizon.
I can handle the failure after 3 tries using the method failed, but how can I handle the timeout event each 3 tries of this job ?
Used for logging and feedback, I want my user to be notified when the first or second try fails and it will be retried later.
class MyJob implements ShouldQueue
{
public $tries = 3;
public $timeout = 600;
// [...]
public function failed(Throwable $exception)
{
// The failure of the 3 tries.
}
// Any method for catching each timeouts ?
}
You may define the $failOnTimeout property on the job class
/**
* Indicate if the job should be marked as failed on timeout.
*
* #var bool
*/
public $failOnTimeout = true;
https://laravel.com/docs/9.x/queues#failing-on-timeout
I dont think there is a method for that,
But you can do something like catch the Error thrown if the job fails and verify that its from timeout exception which I believe would throw the exception handler Symfony\Component\Process\Exception\ProcessTimedOutException.
Something like;
public function handle() {
try {
// run job
} catch (\Throwable $exception) {
// manually fail it if attempt is more than twice
if ($this->attempts() > 2)
$this->fail($exception);
// Check if the error it timeout related
if ( $exception instanceof \Symfony\Component\Process\Exception\ProcessTimedOutException ) {
// Whatever you want to do when it fails due to timeout
}
// release the job back to queue after 5 seconds
$this->release(5);
return;
}
}
Just try running a job and make sure it fails because of timeout, to verify the actual timeout class exception
Ok I found the solution.
TLDR;
Put a pcntl_signal at the beginning of your your job handle() and then you can do something like call a onTimeout() method :
public function handle()
{
pcntl_signal(SIGALRM, function () {
$this->onTimeout();
exit;
});
// [...]
}
public function onTimeout()
{
// This method will be called each
}
The story behind :
The Queue documentation says : The pcntl PHP extension must be installed in order to specify job timeouts.
So, digging into the pcntl PHP documentation I found interesting pcntl_* functions. And a call of pcntl_signal into Illuminate/Queue/Worker.php l221.
It looks that if we register a method using pcntl_signal it replace the previous handler. I tried to load the Laravel one using pcntl_signal_get_handler but I can't manage to call it. So the workaroud is to call exit; so Laravel will consider the process as lost and mark it as timeout (?). There is the 3 tries, the retry_after is respected, and at the last try the job fails ... It may be cleaner to keep the original handler, but as it work well on my case so I will stop investigate.

Laravel 8 - Conditionally remember a value in cache [duplicate]

I'm developing one of my first applications with the Laravel 4 framework (which, by the way, is a joy to design with). For one component, there is an AJAX request to query an external server. The issue is, I want to cache these responses for a certain period of time only if they are successful.
Laravel has the Cache::remember() function, but the issue is there seems to be no "failed" mode (at least, none described in their documentation) where a cache would not be stored.
For example, take this simplified function:
try {
$server->query();
} catch (Exception $e) {
return Response::json('error', 400);
}
I would like to use Cache::remember on the output of this, but only if no Exception was thrown. I can think of some less-than-elegant ways to do this, but I would think that Laravel, being such an... eloquent... framework, would have a better way. Any help? Thanks!
This is what worked for me:
if (Cache::has($key)) {
$data = Cache::get($key);
} else {
try {
$data = longQueryOrProcess($key);
Cache::forever($key, $data); // only stored when no error
} catch (Exception $e) {
// deal with error, nothing cached
}
}
And of course you could use Cache::put($key, $data, $minutes); instead of forever
I found this question, because I was looking for an answer about this topic.
In the meanwhile I found a solution and like to share it here:
(also check out example 2 further on in the code)
<?php
/**
* Caching the query - Example 1
*/
function cacheQuery_v1($server)
{
// Set the time in minutes for the cache
$minutes = 10;
// Check if the query is cached
if (Cache::has('db_query'))
{
return Cache::get('db_query');
}
// Else run the query and cache the data or return the
// error response if an exception was catched
try
{
$query = $server->query(...);
}
catch (Exception $e)
{
return Response::json('error', 400);
}
// Cache the query output
Cache::put('db_query', $query, $minutes);
return $query;
}
/**
* Caching the query - Example 2
*/
function cacheQuery_v2($server)
{
// Set the time in minutes for the cache
$minutes = 10;
// Try to get the cached data. Else run the query and cache the output.
$query = Cache::remember('db_query', $minutes, function() use ($server) {
return $server->query(...);
});
// Check if the $query is NULL or returned output
if (empty($query))
{
return Response::json('error', 400);
}
return $query;
}
I recommend you to use Laravel's Eloquent ORM and/or the Query Builder to operate with the Database.
Happy coding!
We're working around this by storing the last good value in Cache::forever(). If there's an error in the cache update callback, we just pull the last value out of the forever key. If it's successful, we update the forever key.

Laravel Job Batching unable to cancel

I have a simple laravel job batching, my problem is when one of my queue inside batch is failed and throw an exception, it doesn't stop or cancel the execution of the batch even I add the cancel method, still processing the next queue.
this is my handle and failed method
public function handle()
{
if ($this->batch()->cancelled()) {
return;
}
$csv_data = array_map('str_getcsv', file($this->chunk_directory));
foreach ($csv_data as $key => $row) {
if(count($this->header) != count($row)) {
$data = array_combine($this->header, $row);
} else {
$this->batch()->cancel();
throw new Exception("Your file doesn't match the number of headers like your product header");
}
}
}
public function failed(\Exception $e = null)
{
broadcast(new QueueProcessing("failed", BatchHelpers::getBatch($this->batch()->id)));
}
here is my commandline result
[2021-01-11 01:17:57][637] Processing: App\Jobs\ImportItemFile
[2021-01-11 01:17:57][637] Failed: App\Jobs\ImportItemFile
[2021-01-11 01:17:58][638] Processing: App\Jobs\ImportItemFile
[2021-01-11 01:17:58][638] Processed: App\Jobs\ImportItemFile
From the Laravel 8 Queue documentation:
When a job within a batch fails, Laravel will automatically mark the batch as "cancelled"
So the default behavior is that the whole batch is marked as "canceled" and stops executing (note that the currently executing jobs will not be stopped).
In your case if the batch execution is continuing, maybe you've turned on the allowFailures() option when created a batch?
By the way you don't need to call the cancel() method. When an exception is thrown, the given job is already "failed" and the whole batch cancels.
Either remove the cancel() line, or return after the cancelation method (without throwing an exception). (see Cancelling batches)

How to mock a Job object in Laravel?

When it comes to Queue testing in Laravel, I use the provided Queue Fake functionality. However, there is a case where I need to create a Mock for a Job class.
I have the following code that pushes a job to a Redis powered queue:
$apiRequest->storeRequestedData($requestedData); // a db model
// try-catch block in case the Redis server is down
try {
App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');
$apiRequest->markJobQueued();
} catch (\Exception $e) {
//handle the case when the job is not pushed to the queue
}
I need to be able to test the code in the catch block. Because of that, I'm trying to mock the Job object in order to be able to create a faker that will throw an exception.
I tried this in my Unit test:
ProcessRunEndpoint::shouldReceive('dispatch');
That code returns: Error: Call to undefined method App\Jobs\ProcessRunEndpoint::shouldReceive().
I also tried to swap the job instance with a mock object using $this->instance() but it didn't work as well.
That said, how can I test the code in the catch block?
According to the docs, you should be able to test jobs through the mocks provided for the Queue.
Queue::assertNothingPushed();
// $apiRequest->storeRequestedData($requestedData);
// Use assertPushedOn($queue, $job, $callback = null) to test your try catch
Queue::assertPushedOn('run', App\Jobs\ProcessRunEndpoint::class, function ($job) {
// return true or false depending on $job to pass or fail your assertion
});
Making the line App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run'); throw an exception is a bit complicated. dispatch() just returns an object and onQueue() is just a setter. No other logic is done there. Instead, we can make everything fail by messing with the configuration.
Instead of Queue::fake();, override default queue driver with one that just won't work: Queue::setDefaultDriver('this-driver-does-not-exist'); This will make every job dispatch fail and throw an ErrorException.
Minimalist example:
Route::get('/', function () {
try {
// Job does nothing, the handle method is just sleep(5);
AJob::dispatch();
return view('noError');
} catch (\Exception $e) {
return view('jobError');
}
});
namespace Tests\Feature;
use App\Jobs\AJob;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class AJobTest extends TestCase
{
/**
* #test
*/
public function AJobIsDispatched()
{
Queue::fake();
$response = $this->get('/');
Queue::assertPushed(AJob::class);
$response->assertViewIs('noError');
}
/**
* #test
*/
public function AJobIsNotDispatched()
{
Queue::setDefaultDriver('this-driver-does-not-exist');
$response = $this->get('/');
$response->assertViewIs('jobError');
}
}
I found a solution. Instead of using a facade for adding a job to the queue (App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');), I injected it into the action of the controller:
public function store(ProcessRunEndpoint $processRunEndpoint)
{
// try-catch block in case the Redis server is down
try {
$processRunEndpoint::dispatch($apiRequest)->onQueue('run');
} catch (\Exception $e) {
//handle the case when the job is not pushed to the queue
}
}
With this, the job object is resolved from the container, so it can be mocked:
$this->mock(ProcessRunEndpoint::class, function ($mock) {
$mock->shouldReceive('dispatch')
->once()
->andThrow(new \Exception());
});
Although still not sure why the shouldReceive approach doesn't work for the facade: https://laravel.com/docs/8.x/mocking#mocking-facades

unassign order status from status

I need to unassign status from state in migration.
$status->setStatus('approved')
->unassignState(Mage_Sales_Model_Order::STATE_NEW)
->assignState(Mage_Sales_Model_Order::STATE_PROCESSING, false)
->save();
If status is assigned to migration, it works. But if state is not assigned, there is exception and
migrations fails.
What is the best way to solve this problem?
The only solution that occured to me, is to create function trapper that catches exception. I this case
it seems that migration works even if status is not assigned to state.
function unAssignStatusFromState($status, $state)
{
try {
$status->unassignState($state);
return true;
} catch(Exception $e) {
return false;
}
}

Resources