DEV Community

Adam Mateusz Brożyński
Adam Mateusz Brożyński

Posted on

Cronless queue:work in Laravel executed in background

There are some approaches how to execute queue:work but I found them useless. Here is a solution for most shared hostings that:

  • does not require additional route
  • does not require remotely visiting website
  • does not require shell access
  • does not require cron access
  • requires bash with flock on server (for single execution protection)
  • requires PHP exec function available
  • requires shell php command (php-cli installed on server)
  • runs in the background so makes no website slowdowns
  • can be set to execute not more than once between minimum time span of $runEverySec seconds

1. Let's create new Middleware:

$ php artisan make:middleware QueueWorkMiddleware 
Enter fullscreen mode Exit fullscreen mode

2. Use it as global middleware in bootstrap/app.php:

... ->withMiddleware(function (Middleware $middleware) { $middleware->append(App\Http\Middleware\QueueWorkMiddleware::class); }) ... 
Enter fullscreen mode Exit fullscreen mode

3. Put contents to App/Middleware/QueueWorkMiddleware.php:

<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class QueueWorkMiddleware { // lock file that prevents too many executions public $lockFile = 'queue.lock'; // log from queue command public $logFile = 'queue.log'; // log from background exec command public $execLogFile = 'exec.log'; // pid of executed command public $pidFile = 'queue.pid'; // php command path public $phpExec = '/usr/bin/php'; // queue:work command public $queueCmd = 'artisan queue:work --stop-when-empty'; // minimum time in seconds between executions public $runEverySec = 10; /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { // get pid and lock file names $pidFile = base_path()."/".$this->pidFile; $lockFile = base_path()."/".$this->lockFile; if( // if there is no lock file and !file_exists($lockFile) && // there is no pid file (queue was never executed before) // or time between pidfile modification is more or equal $runEverySec (!file_exists($pidFile) || (time()-filemtime(base_path()."/{$this->pidFile}") >= $this->runEverySec)) ) { // do the work $this->work(); } return $next($request); } public function work() { // file names $basePath = base_path(); $lockFile = "{$basePath}/{$this->lockFile}"; $logFile = "{$basePath}/{$this->logFile}"; $execLogFile = "{$basePath}/{$this->execLogFile}"; $pidFile = base_path()."/".$this->pidFile; // main queue command and lock file removal $cmd = "{ {$this->phpExec} {$this->queueCmd} > {$logFile} 2>&1; rm {$lockFile}; }"; // go to base path and run command by flock (this guarantees single execution only!) $cmd = "cd {$basePath} && flock -n {$lockFile} --command '{$cmd}'"; // execute command in background exec(sprintf("%s > {$execLogFile} 2>&1 & echo $! >> %s", $cmd, $pidFile)); return true; } } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)