-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[2.x] Adds scheduler proxy command to normalize sub-minute schedules (#…
…167) * add vapor scheduler proxy * update syntax * fix return * Apply fixes from StyleCI * adds tests * remove console events trait * conditional test * wip * wip * wip * wip * Update VaporScheduleCommand.php --------- Co-authored-by: StyleCI Bot <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
- Loading branch information
1 parent
eb7b7fa
commit 03bc115
Showing
3 changed files
with
179 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
namespace Laravel\Vapor\Console\Commands; | ||
|
||
use Illuminate\Console\Command; | ||
use Illuminate\Contracts\Cache\Repository; | ||
use Illuminate\Support\Str; | ||
|
||
class VaporScheduleCommand extends Command | ||
{ | ||
/** | ||
* The console command name. | ||
* | ||
* @var string | ||
*/ | ||
protected $signature = 'vapor:schedule'; | ||
|
||
/** | ||
* The console command description. | ||
* | ||
* @var string | ||
*/ | ||
protected $description = 'Run the scheduled commands at the beginning of every minute'; | ||
|
||
/** | ||
* Indicates whether the command should be shown in the Artisan command list. | ||
* | ||
* @var bool | ||
*/ | ||
protected $hidden = true; | ||
|
||
/** | ||
* Execute the console command. | ||
*/ | ||
public function handle(): int | ||
{ | ||
if (! $cache = $this->ensureValidCacheDriver()) { | ||
$this->call('schedule:run'); | ||
|
||
return 0; | ||
} | ||
|
||
$key = (string) Str::uuid(); | ||
$lockObtained = false; | ||
|
||
while (true) { | ||
if (! $lockObtained) { | ||
$lockObtained = $this->obtainLock($cache, $key); | ||
} | ||
|
||
if ($lockObtained && now()->second === 0) { | ||
$this->releaseLock($cache); | ||
|
||
$this->call('schedule:run'); | ||
|
||
return 0; | ||
} | ||
|
||
if (! $lockObtained && now()->second === 0) { | ||
return 1; | ||
} | ||
|
||
usleep(10000); | ||
} | ||
} | ||
|
||
/** | ||
* Ensure the cache driver is valid. | ||
*/ | ||
protected function ensureValidCacheDriver(): ?Repository | ||
{ | ||
$manager = $this->laravel['cache']; | ||
|
||
if (in_array($manager->getDefaultDriver(), ['memcached', 'redis', 'dynamodb', 'database'])) { | ||
return $manager->driver(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Obtain the lock for the schedule. | ||
*/ | ||
protected function obtainLock(Repository $cache, string $key): bool | ||
{ | ||
return $key === $cache->remember('vapor:schedule:lock', 60, function () use ($key) { | ||
return $key; | ||
}); | ||
} | ||
|
||
/** | ||
* Release the lock for the schedule. | ||
*/ | ||
protected function releaseLock(Repository $cache): void | ||
{ | ||
$cache->forget('vapor:schedule:lock'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
namespace Laravel\Vapor\Tests\Unit; | ||
|
||
use Carbon\Carbon; | ||
use Illuminate\Cache\Repository; | ||
use Illuminate\Support\Facades\Cache; | ||
use Illuminate\Support\Str; | ||
use Laravel\Vapor\VaporServiceProvider; | ||
use Mockery; | ||
use Orchestra\Testbench\TestCase; | ||
|
||
class VaporScheduleCommandTest extends TestCase | ||
{ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
|
||
Carbon::setTestNow('2021-01-01 00:00:00'); | ||
|
||
Str::createUuidsUsing(function () { | ||
return 'test-schedule-lock-key'; | ||
}); | ||
} | ||
|
||
protected function getPackageProviders($app): array | ||
{ | ||
return [ | ||
VaporServiceProvider::class, | ||
]; | ||
} | ||
|
||
public function test_scheduler_is_invoked_when_invalid_cache_is_configured() | ||
{ | ||
$fake = Mockery::mock(Repository::class); | ||
Cache::shouldReceive('getDefaultDriver')->once()->andReturn('array'); | ||
$fake->shouldNotReceive('remember'); | ||
if (version_compare($this->app->version(), 10, '>=')) { | ||
$fake->shouldReceive('forget')->once()->with('illuminate:schedule:interrupt')->andReturn(true); | ||
} | ||
if (! Str::startsWith($this->app->version(), '9')) { | ||
Cache::shouldReceive('driver')->once()->andReturn($fake); | ||
} | ||
$fake->shouldNotReceive('forget')->with('vapor:schedule:lock'); | ||
|
||
$this->artisan('vapor:schedule') | ||
->assertExitCode(0); | ||
} | ||
|
||
public function test_scheduler_is_called_at_the_top_of_the_minute() | ||
{ | ||
Cache::shouldReceive('getDefaultDriver')->once()->andReturn('dynamodb'); | ||
Cache::shouldReceive('driver')->andReturn($fake = Mockery::mock(Repository::class)); | ||
$fake->shouldReceive('remember')->once()->with('vapor:schedule:lock', 60, Mockery::any())->andReturn('test-schedule-lock-key'); | ||
if (version_compare($this->app->version(), 10, '>=')) { | ||
$fake->shouldReceive('forget')->once()->with('illuminate:schedule:interrupt')->andReturn(true); | ||
} | ||
$fake->shouldReceive('forget')->once()->with('vapor:schedule:lock')->andReturn(true); | ||
|
||
$this->artisan('vapor:schedule') | ||
->assertExitCode(0); | ||
} | ||
|
||
public function test_scheduler_is_not_invoked_if_lock_cannot_be_obtained() | ||
{ | ||
Cache::shouldReceive('getDefaultDriver')->once()->andReturn('dynamodb'); | ||
Cache::shouldReceive('driver')->andReturn($fake = Mockery::mock(Repository::class)); | ||
$fake->shouldReceive('remember')->once()->with('vapor:schedule:lock', 60, Mockery::any())->andReturn('test-locked-schedule-lock-key'); | ||
$fake->shouldNotReceive('forget')->with('illuminate:schedule:interrupt')->andReturn(true); | ||
$fake->shouldNotReceive('forget')->with('vapor:schedule:lock'); | ||
|
||
$this->artisan('vapor:schedule') | ||
->assertExitCode(1); | ||
} | ||
} |