Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.1 failed queued jobs fails on failed() method, prevents queue failure event handler from being called

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?

like image 807
Joel Joel Binks Avatar asked Jul 08 '15 23:07

Joel Joel Binks


2 Answers

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.

Queue::failing(function($connection, $job, $data)
        {
            $command = (unserialize($data['data']['command']));
            $command->failed();
        });
like image 81
ShaunUK Avatar answered Nov 10 '22 17:11

ShaunUK


If you start the queue listener like this

nohup php artisan queue:listen > storage/logs/queue.log 2>&1 &

then a queue log file will be created and populated automatically.

like image 23
aethergy Avatar answered Nov 10 '22 17:11

aethergy