I have a Laravel project with a queue, running ~1000 jobs a day.
When a job is failing, in 5.7, it was moved to failed_jobs table, and I was able to retry it.
After migration to Laravel 5.8, jobs just disappear.
This code should mark a job as failed and put it in the failed-jobs table.
/**
* Execute the job.
*/
public function handle()
{
throw new \Exception('WRONG JOB');
}
Supervisor log in that case:
[2019-04-10 15:07:57][11932] Processing: App\Jobs\ExecuteAction
It seems that execution stops, and Queue::failing event is not called.
This code works, but doesn't seem right to me.
class ExecuteAction implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Execute the job.
*/
public function handle()
{
try {
throw new \Exception('WROOONG');
} catch (\Exception $e) {
if ($this->attempts() < $this->tries) {
$this->release(10);
} else {
$this->fail($e);
}
}
}
}
Queue::failing event is called.
Supervisor log ;
[2019-04-10 15:06:52][11926] Processing: App\Jobs\ExecuteAction
[2019-04-10 15:06:52][11926] Failed: App\Jobs\ExecuteAction
[2019-04-10 15:06:52][11926] Processed: App\Jobs\ExecuteAction
In supervisor conf
command=php /path/to/laravel-project/artisan queue:listen
What I am doing wrong ?
Thank you for your help.
From: https://laravel.com/docs/5.8/queues
The command: queue:listen was removed from the documentation and now the recommended way to work with queues is to run: queue:work.
See: https://laravel.com/docs/5.8/queues#running-the-queue-worker
Or you may use: Laravel Horizon
Related
When a job fails my notification doesn't arrive, and I'm not sure why. In my Job I have:
use Notification;
use App\Notifications\MonitoringMessage;
public function handle()
{
asdf;
}
public function failed(Exception $exception)
{
$message = ':warning: A job failed.';
Notification::route('slack', config('services.slack.webhook'))->notify(new MonitoringMessage($message));
}
The notification is not queued using use Queueable; etc because I've read that that might cause the issue because the job itself is also queued.
The code above will cause the job to fail of course, and I can see it in the failed_jobs table, but the notification is not send. If I put the notification code somewhere else (eg in a controller) and execute it the notification is sent so that code is correct.
Any job is handled through the handle() method. Make sure this method doesn't fail because of a syntax mistake. If your code fails on a syntax error, the rest of you code can't possibly be executed as it will never be processed. Force a conceptual mistake, for example a division by zero forced error:
use Notification;
use App\Notifications\MonitoringMessage;
public function handle()
{
$result = 1/0;
}
public function failed()
{
$message = ':warning: A job failed.';
Notification::route('slack', config('services.slack.webhook'))->notify(new MonitoringMessage($message));
}
As you have stated yourself, the Exception class implementation is not needed because it's not used.
I know that I can retry jobs that have failed in my Laravel application by using: php artisan queue:retry 5 OR php artisan queue:retry all to push them back onto the queue.
What I would like to achieve though is to only retry failed jobs from a single queue. Such as php artisan queue:retry all --queue=emails which does not work.
I could however go through each manually by ID php artisan queue:retry 5 but this does not help if I have 1000's of records.
So in summary, my question is, how can I retry all failed jobs on specific queue?
Maybe you can create another command
lets say
command : php artisan example:retry_queue emails
class RetryQueue extends Command
{
protected $signature = 'example:retry_queue {queue_name?}';
protected $description = 'Retry Queue';
public function __construct()
{
parent::__construct();
}
public function handle()
{
// if the optional argument is set, then find all with match the queue name
if ($this->argument('queue_name')) {
$queueList = FailedJobs::where('queue', $this->argument('queue_name'))->get();
foreach($queueList as $list) {
Artisan::call('queue:retry '.$list->id);
}
} else {
Artisan::call('queue:retry all');
}
}
}
I have a table users with 5000 records (5000 users) and in my server i can just send 1000 emails every hour.
How i can send 1000 emails every hour using queue ?
or how to make queues sleep inside loop?
EmailController:
class EmailController extends Controller
{
public function sendEmail(Request $request){
$event=(object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];
$users=User::all();
App\Jobs\SendReminderEmail::dispatch($users,$event)
}
}
SendReminderEmail
class SendReminderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $event;
public $email;
public $users;
public function __construct($users,$event)
{
$this->users = $users;
$this->event = $event;
}
public function handle()
{
foreach ($this->users as $user) {
Mail::to($user->email)->queue(new Reminder($this->event));
}
}
}
Laravel has a neat feature which fits your case perfectly, it's called Task Scheduling, instead of making the job sleep for one hour, you could, instead, call it every hour.
To do so add the job schedule to the schedule() method located on App\Console\Kernel like this:
protected function schedule(Schedule $schedule)
{
$schedule->job(new SendReminderEmail)->hourly();
}
I also would recommend you to make the job self contained, that will make this task much simpler, I'm thinking in something like this:
class SendReminderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $event;
public $users;
public function __construct()
{
$this->users = User::all();
$this->event = (object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];;
}
public function handle()
{
foreach ($this->users as $user) {
Mail::to($user->email)->queue(new Reminder($this->event));
}
}
}
Now you can just get rid of your controller because this job will be executed every hour automatically.
Remember that you will need to run a cron on your server to check if the job needs to be executed. You can also run it manually if you want to test it using php artisan schedule:run.
Hope this helps you.
Laravel has a built-in throttle feature for rate-limited jobs. From the docs:
If your application interacts with Redis, you may throttle your queued
jobs by time or concurrency. This feature can be of assistance when
your queued jobs are interacting with APIs that are also rate limited.
For example, using the throttle method, you may throttle a given type
of job to only run 10 times every 60 seconds. If a lock can not be
obtained, you should typically release the job back onto the queue so
it can be retried later:
Redis::throttle('key')->allow(10)->every(60)->then(function () {
// Job logic...
}, function () {
// Could not obtain lock...
return $this->release(10);
});
In your case, that might look like Redis::throttle(...)->allow(1000)->every(3600)->...
If you're not using Redis, another possible solution which is specific to queued mail is to add a delay. Again, from the docs:
Delayed Message Queueing
If you wish to delay the delivery of a queued
email message, you may use the later method. As its first argument,
the later method accepts a DateTime instance indicating when the
message should be sent:
$when = now()->addMinutes(10);
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->later($when, new OrderShipped($order));
Using this method would require you to calculate the delay for each email during the loop that dispatches emails to the queue. For example, every 1000th iteration of the loop you could increase the delay by 1 hour.
If you're using the Amazon SQS queue service, the same maximum delay of 15 minutes that applies to other queued jobs might also apply here (I'm not sure on this part), in which case you would have to come up with another solution for checking if you're over your rate limit and releasing the job back to the queue.
I parse some HTML pages and API endpoints, for example, every 5 minutes to track changes. For this purpose, I have created ParseJob where I do parsing and save changes to a database. ParseJob implements interface ShouldQueue and I have changed queue driver to Redis. In order to run the ParseJob on a regular basis, I have created ParseCommand and added it to schedule:
class ParseCommand extends Command
{
protected $signature = 'application:my-parse-command';
public function handle()
{
$this->dispatch(new ParseJob());
}
}
class Kernel extends ConsoleKernel
{
protected $commands = [
Commands\ParseCommand::class
];
protected function schedule(Schedule $schedule)
{
$schedule->command('application:my-parse-command')
->everyFiveMinutes();
}
}
And the queue worker is started as a daemon to process the queue. So, every 5 minutes ParseJob is pushed to the queue and the queue worker is processing the job.
Sometimes queue worker process crashes, freezes or for other reasons is not working. But jobs every 5 minutes are pushed into the queue. After an hour of downtime, I have 12 jobs in the queue but they are for that time irrelevant because I do not need to parse 12 times at a certain time, I want just one parse job.
So I want to set TTL for a job that works like expire command in Redis. How to do that? Or maybe you have an alternative solution?
As far a i know it is not possible to set explicitly a Job expiration into Laravel queues. A solution could be setting an expires_at property within your ParseJob and check before executing:
class ParseCommand extends Command
{
protected $signature = 'application:my-parse-command';
public function handle()
{
$this->dispatch(new ParseJob(Carbon::now()->addMinutes(5)));
}
}
then in your Job class
class ParseJob {
protected $expires_at;
public function __construct(Carbon $expires_at) {
$this->expires_at = $expires_at;
}
public function handle()
{
if(!Carbon::now()->gt($this->expires_at)) {
// Parse data
}
}
}
At larvel 8/9 in job
MyJObClass
public function retryUntil(): Carbon
{
return now()->addMinutes(10);
}
Laravel :: Queues #time-based-attempts
I am trying to send emails in laravel 5.1 by using queues. When running queue listen command on terminal,
php artisan queue:listen
Displays below error on terminal,
[Illuminate\Contracts\Queue\EntityNotFoundException]
Queueable entity [App\Setting] not found for ID [].
Values of jobs table is not process. Any idea ?
How can I process my queue ?
I know this question is a few months old, but I'd like to add an observation of mine while encountering this very same error message. It is due to the EventListener (interface of ShouldQueue in this example for asynchronous) not being able to resolve a dependant variable correctly (out of scope or not included in scope of Event object passed through the handle(Event $event) method of EventListener).
For me, this error was fired when I put my code within the __construct block within the EventListener:
public function __construct(Event $event)
{
$localProperty = $event->property
Mail::queue(etc...);
}
public function handle()
{
// Yeah I left this blank... whoops
}
Instead, the handle() method of the EventListener takes an Event interface and when called processes the job in the queue:
In the Event:
public function __construct(Object $ticket, AnotherObject $user)
{
$this->ticket = $ticket;
$this->user = $user;
}
And in Event Listener
class SomeEventListener implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
public function __construct()
{
// Leave me blank!
}
public function handle(Event $event)
{
$someArray = [
'ticket' = $event->ticket,
'user' = $event->user,
];
Mail::queue('some.view', $someArray, function($email) use ($someArray) {
// Do your code here
});
}
}
Although a tad late, I hope this helps someone. Queues are similar to Events (with the exception of Jobs being the main driving force behind Queues), so most of this should be relevant.
Turned out that it was because a model was added to the queue, that has since been deleted.