Laravel 9.38 introduced the concept of Isolatable commands, console commands which cannot be run if another instance of the command is already running. I finally had the opportunity to test this out this week, and it works great!
Creating the command
You have to be running a cache provider to make use of this feature. The cache is where Laravel stores the lock telling it that the command is running. You can use any of the following cache drivers: memcached
, redis
, dynamodb
, database
, file
, or array
.
Here is a sample command. The only difference between this command and a non-isolatable command is the implements Isolatable
added to the class definition.
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Isolatable; class IsolatableCommand extends Command implements Isolatable { protected $signature = 'test:isolate'; protected $description = 'Example isolated command'; public function handle(): void { $this->info("Start"); sleep(10); $this->info('End'); } }
Executing a command
To run an isolated command, you have to include the --isolated
flag.
php artisan test:isolate --isolated
If the command isn’t running in another process, it will run. If the command is already running, you’ll receive an error message:
The [test:isolate] command is already running.
Sample Use Case
There’s a use-case for isolatable commands built into Laravel, the migrate command. You can run php artisan migrate --isolated
to make sure your database migrations don’t run more than once if your application has more than one server in a cluster.
Managing Lock Timeouts
By default, the lock is removed from isolatable commands as soon as the command is done executing. If the command is interrupted or fails to finish, the lock will be lifted after 1 hour.
You can change this time by adding a isolationLockExpiresAt
function to your command class. This function would change the lock expiration from 1 hour to 5 minutes:
public function isolationLockExpiresAt() { return now()->addMinutes(5); }