I am testing the queue functions in Laravel 5.1. I can make jobs queue up in my db table, called jobs, and I can get them to run successfully. I also created a queue failure table called failed_jobs. To test it, inside the jobs table I manipulate the payload data to make it fail then I run the queue worker daemon like so, so it will put the job in the failed_jobs table after one failed attempt:
php artisan queue:work --daemon --tries=1 --queue=myqueue
When the job fails it is immediately put into failed_jobs table as expected.
FYI I have set things up just like the Laravel 5.1 docs recommend:
http://laravel.com/docs/5.1/queues#dealing-with-failed-jobs
I have tried registering my queue failure event in the AppServiceProvider's boot() method as outlined in the docs:
Queue::failing(function ($connection, $job, $data) {
Log::error('Job failed!');
});
I have also tried the failed() method inside the actual job scripts like so:
/**
* Handle a job failure.
*
* @return void
*/
public function failed()
{
Log::error('failed!');
}
Either way, neither of these events is triggered when the queued job fails. I see nothing in the logs except for the exception stack trace which I made occur on purpose. Does Laravel 5.1 have a bug here or am I missing something?
UPDATE:
I have done some more research. When a queue job failure occurs, the logic for handling that failure is in vendor/laravel/framework/src/Illuminate/Queue/Worker.php:
protected function logFailedJob($connection, Job $job)
{
if ($this->failer) {
$this->failer->log($connection, $job->getQueue(), $job->getRawBody());
$job->delete();
$job->failed();
$this->raiseFailedJobEvent($connection, $job);
}
return ['job' => $job, 'failed' => true];
}
What happens is the failed() function never executes and it prevents the next function, raisedFailedJobEvent() from being called. It's as if the script silently halts when failed() is called. Now if I reverse the order of these lines, I can get the raiseFailedJobEvent() to fire and if I register a queue event handler in EventServiceProvider.php or AppServiceProvider.php, I can verify it gets fired and I can successfully handle the event. Unfortunately, having failed() before raiseFailedJobEvent() prevents this event from ever occurring.
UPDATE:
The problem seems to stem from how I make it fail. If I deliberately corrupt the data in the job queue table, the failed() method never gets called. There is a stack trace in the logs:
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(8, 'unserialize():
If I actually go into vendor/laravel/framework/src/Illuminate/Queue/Worker.php and force it to fail every time it runs (in an exception-less way of course), then failure() gets called. The problem, obviously, is how do I know how this queue will behave in a real-world failure? If corrupt db data causes a failure yet prevents failure() from being called, this is no good. What if there is an actual corruption of db queue data in the real world?
If you start the queue listener like this
then a queue log file will be created and populated automatically.
Try this out from my conversation with Graham at https://github.com/laravel/framework/issues/9799
In the end the most elegant solution I could find to get the the failed() method to trigger on the job class itself was to add to the below to the boot() method of EventServiceProvider.php. Catching the full fail event that is fired and then digging out the command/job and unserializing it to call the failed() method.