Sjoerd de Wit
Sjoerd de Wit

Reputation: 2413

Laravel dispatch job doesn't run async, hinders execution

so in laravel I have a function that posts some content and pushes a job onto a queue and returns some data from the function(not the queue).

Now I thought that laravel queues were supposed to be some sort of async function that makes sure your code remains quick even though there is a lot of data to handle so you put it in a job. But I noticed that my code still needs to wait till the job is finished before it returns data to the user. Here's a log of the script called 2 times but one is with almost no data in the job, and the other has a lot:

[2016-09-08 13:26:50] production.INFO: New alert with Image  
[2016-09-08 13:26:50] production.INFO: Push is send.  
[2016-09-08 13:26:50] production.INFO: Alert data is send back.  
[2016-09-08 13:26:50] production.INFO: New image is valid  
[2016-09-08 13:26:50] production.INFO: Move file for upload  
[2016-09-08 13:26:50] production.INFO: Made it to upload  

[2016-09-08 13:28:50] production.INFO: New alert with Image  
[2016-09-08 13:31:19] production.INFO: Push is send.  
[2016-09-08 13:31:19] production.INFO: Alert data is send back.  
[2016-09-08 13:31:20] production.INFO: New image is valid  
[2016-09-08 13:31:20] production.INFO: Move file for upload  
[2016-09-08 13:31:20] production.INFO: Made it to upload  

As you can see the second ones takes 4 minutes before the application receives further data. This is a deal breaker you can't make users wait so long. So how do I get the job to run async and the function doesn't have to wait for it to finish.

Here's the line where I call the code:

if($isValid === true) {
  $this->dispatch(new SendPushNotificationAlert($alert));

  Log::info('Push is send.');
}

And here is my job:

<?php

namespace App\Jobs;

use DB;
use Log;
use App\Alerts;
use App\Users;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Http\Controllers\PushNotificationsController as PushNotificationsController;

class SendPushNotificationAlert extends Job implements ShouldQueue
{
    use InteractsWithQueue, SerializesModels;
    Protected $alert;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Alerts $alert)
    {
        //
        $this->alert = $alert;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $alert = $this->alert;
        $radius = 10000;
        $earthRadius = 6371000;  // earth's mean radius, m
        // first-cut bounding box (in degrees)
        $maxLat = $alert->lat + rad2deg($radius/$earthRadius);
        $minLat = $alert->lat - rad2deg($radius/$earthRadius);
        // compensate for degrees longitude getting smaller with increasing latitude
        $maxLon = $alert->lon + rad2deg($radius/$earthRadius/cos(deg2rad($alert->lat)));
        $minLon = $alert->lon - rad2deg($radius/$earthRadius/cos(deg2rad($alert->lat)));
        $locations = DB::select("
        SELECT id, lat, lon, user_id, name, radius,
          acos(
            sin(?)*
            sin(radians(lat))+
            cos(?)*
            cos(radians(lat))*
            cos(radians(lon) - ?)
          ) * ? AS distance
              FROM (
                  SELECT id, lat, lon, user_id, name, radius
                  FROM user__locations
                  WHERE lat BETWEEN ? AND ?
                    AND lon BETWEEN ? AND ? AND hide = 0
        ) AS FirstCut
        WHERE acos(
            sin(?)*
            sin(radians(lat))+
            cos(?)*
            cos(radians(lat)
          )*
          cos(radians(lon) - ?)
        ) * ? < ?
        ORDER BY distance", [
          deg2rad($alert->lat),
          deg2rad($alert->lat),
                deg2rad($alert->lon),
          $earthRadius,
                $minLat,
          $maxLat,
          $minLon,
          $maxLon,
          deg2rad($alert->lat),
          deg2rad($alert->lat),
          deg2rad($alert->lon),
          $earthRadius,
          $radius
        ]);

        if(count($locations > 0)) {
          foreach ($locations as $location) {
            if($location->distance < $location->radius) {
              if($alert->anoniem == 0) {
                $user = Users::find($alert->user_id);
                $pushuser = Users::find($location->user_id);
                $pushuser->type = 'location';
                $pushuser->locationname = $location->name;
                $pushusers[] = $pushuser;
              }
                    }
          }
        }
        if(isset($pushusers)) {
          PushNotificationsController::sendPushnotificationsAlert($pushusers, $user);
        }
    }
}

Anyone knows why the job doesn't run async and what I could do to fix it?

Upvotes: 9

Views: 15311

Answers (3)

Sami
Sami

Reputation: 8419

For laravel >= 6.14, you can use

$array_or_object = [];
YourJobName::dispatchAfterResponse($array_or_object);

Ref: https://laravel.com/docs/8.x/queues#dispatching-after-the-response-is-sent-to-browser

To have properly structured job you need

php artisan make:job YourJobName

Ref: https://laravel.com/docs/8.x/queues#generating-job-classes

Example

<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;

use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;

class YourJobName implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    protected $received_object;    
    public function __construct($received_data)
    {
        $this->received_object = $received_data;
    }    
    public function handle()
    {
        //do_some_thing($this->received_object);
    }
}

Find it explained in easy wording: https://divinglaravel.com/running-a-task-after-the-response-is-sent

Upvotes: 2

Hyder B.
Hyder B.

Reputation: 12286

I have been getting the same issue in Laravel 5.7. By default, Laravel queue is configured to run synchronously. Open config/queue.php, switch to anything else other than sync

For example:

'default' => env('QUEUE_CONNECTION', 'redis'),

Alternatively, you can also add this to your .env file.

QUEUE_CONNECTION=redis

Upvotes: 7

Sven Buijsrogge
Sven Buijsrogge

Reputation: 149

What queue driver do you use?

As described here: https://laravel.com/docs/5.2/queues#introduction you have multiple options but if you use synchronous (for local environments only) it'll not run Async.

And does your Job implement the Illuminate\Contracts\Queue\ShouldQueue?

As described here: https://laravel.com/docs/5.2/queues#writing-job-classes you'll see that if you don't implement it the job will run synchronously.

Hope it'll help!

EDIT

Saw you already implement the Illuminate\Contracts\Queue\ShouldQueue, so that could not be the problem.

Upvotes: 1

Related Questions