laravelqueuelaravel-8laravel-jobs

laravel queue:work are not processing until I run the job synchronously


Laravel version 8.83.17.

I've been using laravel for years and this seems to be a weird problem only occured in one of my mac machine.

Queue driver is using local Redis. have set QUEUE_CONNECTION=redis. And connection is ok.

Then Run php artisan queue:work --queue=test on one of the command window

class TestJob implements ShouldQueue {
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct() {}

    public function handle() {
        Log::debug('TestJobRun');
    }
}

Running TestJob::dispatch()->onQueue('test') on tinker to dispatch a simple job TestJob. Nothing happend. until I run TestJob::dispatchSync(), suddenly the previous job fired together. with the logs 'TestJobRun'

[2023-09-14 19:09:15][a4kFc8yYeVg3agjsFnYP0K5BYbH9NtiD] Processing: App\Jobs\TestJob
[2023-09-14 19:09:15][a4kFc8yYeVg3agjsFnYP0K5BYbH9NtiD] Processed:  App\Jobs\TestJob

I just dont know why. how to debug this?

I tried thanging queue, sleep, timeout settings for queue:work. Tried swiching to queue:listen or supervisor ways. Nothing helped.


Solution

  • This is not related to the queue you're trying to use. It is related to trying to dispatch a job from tinker.

    The dispatch() method creates a PendingDispatch object instance. The job is sent to the queue from the destructor of the PendingDispatch instance, so the job will not be queued until this instance is destroyed.

    When running commands in tinker, the result of the last command is kept in memory. Because the result of your last command was the PendingDispatch instance, and it is being kept in memory, the destructor is not being called on it, and the job is not dispatching.

    The next command you run will push the PendingDispatch instance out of memory, call the destructor, and your job will be sent to the queue.

    This is why your second call to TestJob::dispatchSync() dispatched two jobs. The first one was the one from the PendingDispatch instance finally being destroyed, and the second one was the one from the call to TestJob::dispatchSync() (which does not use a PendingDispatch instance).

    In tinker, instead of using the ::dispatch() method on the job, you can use the Bus::dispatch() method directly.

    Bus::dispatch((new TestJob())->onQueue('test'));
    

    Or, you can append another command to your dispatch command so that the result of your command isn't the PendingDispatch instance. Ex:

    TestJob::dispatch()->onQueue('test');echo;
    

    Now the result of your command will be the result of the appended echo; instead of the PendingDispatch instance returned from the distpatch(). Therefore, the PendingDispatch instance will immediately be destroyed and your job will dispatch.


    NB: this is also noted in the Laravel documentation:

    The dispatch helper function and dispatch method on the Dispatchable class depends on garbage collection to place the job on the queue. Therefore, when using tinker, you should use Bus::dispatch or Queue::push to dispatch jobs.