Search code examples
phplaravelredisqueuedelay

How to cancel queued job in Laravel or Redis


How can I browse all the pending jobs within my Redis queue so that I could cancel the Mailable that has a certain emailAddress-sendTime pair?

I'm using Laravel 5.5 and have a Mailable that I'm using successfully as follows:

$sendTime = Carbon::now()->addHours(3);
Mail::to($emailAddress)
      ->bcc([config('mail.supportTeam.address'), config('mail.main.address')])
                    ->later($sendTime, new MyCustomMailable($subject, $dataForMailView));

When this code runs, a job gets added to my Redis queue.

I've already read the Laravel docs but remain confused.

How can I cancel a Mailable (prevent it from sending)?

I'd love to code a webpage within my Laravel app that makes this easy for me.

Or maybe there are tools that already make this easy (maybe FastoRedis?)? In that case, instructions about how to achieve this goal that way would also be really helpful. Thanks!

Update:

I've tried browsing the Redis queue using FastoRedis, but I can't figure out how to delete a Mailable, such as the red arrow points to here: enter image description here

UPDATE:

Look at the comprehensive answer I provided below.


Solution

  • Comprehensive Answer:

    I now use my own custom DispatchableWithControl trait instead of the Dispatchable trait.

    I call it like this:

    $executeAt = Carbon::now()->addDays(7)->addHours(2)->addMinutes(17);
    SomeJobThatWillSendAnEmailOrDoWhatever::dispatch($contactId, $executeAt);
    

    namespace App\Jobs;
    
    use App\Models\Tag;
    use Carbon\Carbon;
    use Exception;
    use Illuminate\Bus\Queueable;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Log;
    
    class SomeJobThatWillSendAnEmailOrDoWhatever implements ShouldQueue {
    
        use DispatchableWithControl,
            InteractsWithQueue,
            Queueable,
            SerializesModels;
    
        protected $contactId;
        protected $executeAt;
    
        /**
         * 
         * @param string $contactId
         * @param Carbon $executeAt
         * @return void
         */
        public function __construct($contactId, $executeAt) {
            $this->contactId = $contactId;
            $this->executeAt = $executeAt;
        }
    
        /**
         * Execute the job. 
         *
         * @return void
         */
        public function handle() {
            if ($this->checkWhetherShouldExecute($this->contactId, $this->executeAt)) {
                //do stuff here
            }
        }
    
        /**
         * The job failed to process. 
         *
         * @param  Exception  $exception
         * @return void
         */
        public function failed(Exception $exception) {
            // Send user notification of failure, etc...
            Log::error(static::class . ' failed: ' . $exception);
        }
    
    }
    

    namespace App\Jobs;
    
    use App\Models\Automation;
    use Carbon\Carbon;
    use Illuminate\Foundation\Bus\PendingDispatch;
    use Log;
    
    trait DispatchableWithControl {
    
        use \Illuminate\Foundation\Bus\Dispatchable {//https://stackoverflow.com/questions/40299080/is-there-a-way-to-extend-trait-in-php
            \Illuminate\Foundation\Bus\Dispatchable::dispatch as parentDispatch;
        }
    
        /**
         * Dispatch the job with the given arguments.
         *
         * @return \Illuminate\Foundation\Bus\PendingDispatch
         */
        public static function dispatch() {
            $args = func_get_args();
            if (count($args) < 2) {
                $args[] = Carbon::now(TT::UTC); //if $executeAt wasn't provided, use 'now' (no delay)
            }
            list($contactId, $executeAt) = $args;
            $newAutomationArray = [
                'contact_id' => $contactId,
                'job_class_name' => static::class,
                'execute_at' => $executeAt->format(TT::MYSQL_DATETIME_FORMAT)
            ];
            Log::debug(json_encode($newAutomationArray));
            Automation::create($newAutomationArray);
            $pendingDispatch = new PendingDispatch(new static(...$args));
            return $pendingDispatch->delay($executeAt);
        }
    
        /**
         * @param int $contactId
         * @param Carbon $executeAt
         * @return boolean
         */
        public function checkWhetherShouldExecute($contactId, $executeAt) {
            $conditionsToMatch = [
                'contact_id' => $contactId,
                'job_class_name' => static::class,
                'execute_at' => $executeAt->format(TT::MYSQL_DATETIME_FORMAT)
            ];
            Log::debug('checkWhetherShouldExecute ' . json_encode($conditionsToMatch));
            $automation = Automation::where($conditionsToMatch)->first();
            if ($automation) {
                $automation->delete();
                Log::debug('checkWhetherShouldExecute = true, so soft-deleted record.');
                return true;
            } else {
                return false;
            }
        }
    
    }
    

    So, now I can look in my 'automations' table to see pending jobs, and I can delete (or soft-delete) any of those records if I want to prevent the job from executing.