How to know if bus chain has failed or aborted? laravel - laravel

I have my sample code below, the bus is just working fine but I was wondering if it's possible to check if a bus chain has aborted or failed.
I tried assigning a variable but the assignment inside the catch function doesn't seem to work. Do we have predefined bus function or variable to determine it? I tried $bus->failedJobs but did not work as well.
$hasFailed = false;
$bus = Bus::chain($batches)->catch(function (Throwable $e) use ($hasFailed) {
$hasFailed = true;
})->dispatch();
return $hasFailed ? "failed" : "not";

$hasFailed inside the catch function won't work because it creates a copy of the variable and won't affect the original value outside the function.
You can use an inherited reference to the variable by using &$hasFailed, which allows you to update the value of the variable in the parent context:
$hasFailed = false;
$bus = Bus::chain($batches)
->catch(function (Throwable $e) use (&$hasFailed) {
$hasFailed = true;
})
->dispatch();
return $hasFailed ? "failed" : "not";
You can also use the onQueue method to dispatch the jobs in the chain to a specific queue and use Laravel Horizon to monitor the queue for failed jobs:
$bus = Bus::chain($batches)
->onConnection('redis')
->onQueue('my-bus-chain')
->dispatch();

I just realized that the queue runs in the background and is not running asynchronously. So therefore, there is no way you could determine if it has failed (outside the bus::chain clause) if the queue's job execution is not yet finished.
short answer: it's not possible.
let me know if my understanding is incorrect :)

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.

How to call $this->emitSelf() multiple times on function

I have the following example function:
public function backupService()
{
$job = Job::find($this->job_id);
sleep(5);
$job->status = 'in_progress';
$job->update();
$this->emitSelf('refreshComponent');
sleep(10);
$job->status = 'finished';
$job->update();
$this->emitSelf('refreshComponent');
}
When I change the status to 'in_progress' it changes in my database but doesn't update the component. Apparently it is only issuing $this->emitSelf() when the backupService() function finishes, ie the status will never appear as 'in_progress', only as 'finished'.
I don't want to use the wire:poll directive because I don't want to keep updating the page all the time, only when I specifically call it. How can I resolve this?
The event will be emitted once the entire method backupService() is finished with its execution, when the response from that method is sent back to the browser. Livewire-events are actually sent to the browser with the response, and any components listening for those events will be triggering actions on the client, making secondary-requests.
This means that the refresh-event that you emit, will trigger after everything is completed.
If you don't want to use polling, then another alternative is websockets. But this too can be a bit much for such a simple task, so a third alternative is to restructure your method into two methods, one that starts the process, and have events going from there. Something like this, where the first method is only responsible for setting the new status and emitting a new event that will be starting the job, and the second method is responsible for execution.
protected $listeners = [
'refreshComponent' => '$refresh',
'runJob'
];
public function backupService()
{
$job = Job::find($this->job_id);
$job->status = 'in_progress';
$job->update();
$this->emitSelf('runJob', $job);
}
public function runJob(Job $job)
{
sleep(10);
$job->status = 'finished';
$job->update();
$this->emitSelf('refreshComponent');
}

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)

Is there a way in laravel to manually finish the job within handle method?

The job automatically accomplishes when it reaches the end of the handle method without any exceptions or intervention.
public function handle(){
if($problem){
// manage the problem
return $this->release(5);
}else{
// do nothing and the job will finish anyways
}
}
But is there a manually way to finish the job, like
public function handle(){
if($problem){
return $this->release(5);
}else{
return $this->finish();
}
}
Maybe I overlooked it but I couldn't find such a piece of information.

React dispatcher WAITFOR

I'm trying to use the waitFor function of react.js but it seems I'm doing something wrong.
What I want to do i basic, wait for a store to be filled before calling it from another store.
1.Register token in the first store
RipplelinesStore.dispatcherIndex= Dispatcher.register(function(payload) {
var action = payload.action;
var result;
switch(action.actionType) {
case Constants.ActionTypes.ASK_RIPPLELINES:
registerAccount(action.result);
RipplelinesStore.emitChange(action.result);
break;
}
});
2.Write the wait for in the other store
Dispatcher.register(function(payload) {
var action = payload.action;
var result;
switch(action.actionType) {
case Constants.ActionTypes.ASK_RIPPLEACCOUNTOVERVIEW:
console.log("overviewstore",payload);
Dispatcher.waitFor([
RipplelinesStore.dispatcherIndex,
]);
RippleaccountoverviewsStore.test= RipplelinesStore.getAll();
console.log(RippleaccountoverviewsStore.test);
break;
}
return true;
});
Unfortunately my getall() method return an empty object (getAll() is well written). So it seems that the waitFor dispatcher function is not working.
Basically I know that's because the first store is still receiving the answer from the server but I thought that waitFor would waitfor it to be fetched I don't get it.
Any clue ? Thanks!
Edit: I fire the first store fetch like tha. What I don't understand is that I'm dispatching the load once my backbone collection has fetched (I dispatch on succeed with a promise...)
ripplelinescollection.createLinesList(toresolve.toJSON()).then(function() {
Dispatcher.handleViewAction({
actionType: Constants.ActionTypes.ASK_RIPPLELINES,
result: ripplelinescollection
});
});
I also tried to bind the waitfor to an action which is never called but the other store is still not waiting ! WEIRD !
seems like the problem is the async fetch from the server. waitFor isn't supposed to work this way. You will have to introduce another action that is triggered as soon as the data has been received from the server.
Have a look at this answer: https://stackoverflow.com/a/27797444/1717588

Resources