diff --git a/src/Illuminate/Contracts/Queue/UniqueJob.php b/src/Illuminate/Contracts/Queue/ShouldBeUnique.php similarity index 69% rename from src/Illuminate/Contracts/Queue/UniqueJob.php rename to src/Illuminate/Contracts/Queue/ShouldBeUnique.php index 603be95f5d42..b21643413490 100644 --- a/src/Illuminate/Contracts/Queue/UniqueJob.php +++ b/src/Illuminate/Contracts/Queue/ShouldBeUnique.php @@ -2,7 +2,7 @@ namespace Illuminate\Contracts\Queue; -interface UniqueJob +interface ShouldBeUnique { // } diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index 98c36b3504b4..9495dc15c114 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -5,7 +5,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Contracts\Cache\Repository as Cache; -use Illuminate\Contracts\Queue\UniqueJob; +use Illuminate\Contracts\Queue\ShouldBeUnique; class PendingDispatch { @@ -131,7 +131,7 @@ public function afterResponse() */ protected function shouldDispatch() { - if (! ($this->job instanceof UniqueJob)) { + if (! $this->job instanceof ShouldBeUnique) { return true; } @@ -139,12 +139,14 @@ protected function shouldDispatch() ? $this->job->uniqueId() : ($this->job->uniqueId ?? ''); - $lock = Container::getInstance()->make(Cache::class)->lock( - $key = 'unique:'.get_class($this->job).$uniqueId, - $this->job->uniqueFor ?? 0 - ); + $cache = method_exists($this->job, 'uniqueVia') + ? $this->job->uniqueVia() + : Container::getInstance()->make(Cache::class); - return (bool) $lock->get(); + return (bool) $cache->lock( + $key = 'laravel_unique_job:'.get_class($this->job).$uniqueId, + $this->job->uniqueFor ?? 0 + )->get(); } /** @@ -169,7 +171,7 @@ public function __call($method, $parameters) public function __destruct() { if (! $this->shouldDispatch()) { - // Do nothing. + return; } elseif ($this->afterResponse) { app(Dispatcher::class)->dispatchAfterResponse($this->job); } else { diff --git a/src/Illuminate/Foundation/Console/stubs/job.queued.stub b/src/Illuminate/Foundation/Console/stubs/job.queued.stub index 0be12826fb73..4b78746d8ac8 100644 --- a/src/Illuminate/Foundation/Console/stubs/job.queued.stub +++ b/src/Illuminate/Foundation/Console/stubs/job.queued.stub @@ -3,8 +3,8 @@ namespace {{ namespace }}; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Contracts\Queue\UniqueJob; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; diff --git a/src/Illuminate/Queue/CallQueuedHandler.php b/src/Illuminate/Queue/CallQueuedHandler.php index ec3bfa48ea27..8647e39bc758 100644 --- a/src/Illuminate/Queue/CallQueuedHandler.php +++ b/src/Illuminate/Queue/CallQueuedHandler.php @@ -8,7 +8,7 @@ use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Queue\Job; -use Illuminate\Contracts\Queue\UniqueJob; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Pipeline\Pipeline; use ReflectionClass; @@ -160,22 +160,28 @@ protected function ensureSuccessfulBatchJobIsRecorded($command) } /** - * Ensure the lock for the unique job is released. + * Ensure the lock for a unique job is released. * * @param mixed $command * @return void */ protected function ensureUniqueJobLockIsReleased($command) { - if ($command instanceof UniqueJob) { - $uniqueId = method_exists($command, 'uniqueId') - ? $command->uniqueId() - : ($command->uniqueId ?? ''); - - $this->container->make(Cache::class) - ->lock('unique:'.get_class($command).$uniqueId) - ->forceRelease(); + if (! $command instanceof ShouldBeUnique) { + return; } + + $uniqueId = method_exists($command, 'uniqueId') + ? $command->uniqueId() + : ($command->uniqueId ?? ''); + + $cache = method_exists($command, 'uniqueVia') + ? $command->uniqueVia() + : $this->container->make(Cache::class); + + $cache->lock( + 'laravel_unique_job:'.get_class($command).$uniqueId + )->forceRelease(); } /** diff --git a/tests/Console/Scheduling/FrequencyTest.php b/tests/Console/Scheduling/FrequencyTest.php index a403f79b1c1f..a3cf0a67ae79 100644 --- a/tests/Console/Scheduling/FrequencyTest.php +++ b/tests/Console/Scheduling/FrequencyTest.php @@ -4,6 +4,7 @@ use Illuminate\Console\Scheduling\Event; use Illuminate\Console\Scheduling\EventMutex; +use Illuminate\Support\Carbon; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -91,7 +92,7 @@ public function testMonthlyOn() public function testLastDayOfMonth() { - $this->assertSame('0 0 31 * *', $this->event->lastDayOfMonth()->getExpression()); + $this->assertSame('0 0 '.Carbon::now()->endOfMonth()->day.' * *', $this->event->lastDayOfMonth()->getExpression()); } public function testTwiceMonthly() diff --git a/tests/Integration/Queue/UniqueJobTest.php b/tests/Integration/Queue/UniqueJobTest.php index 45c09338ae40..a6f2c1a5143b 100644 --- a/tests/Integration/Queue/UniqueJobTest.php +++ b/tests/Integration/Queue/UniqueJobTest.php @@ -4,8 +4,8 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Cache\Repository as Cache; +use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Contracts\Queue\UniqueJob; use Illuminate\Database\Schema\Blueprint; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; @@ -21,6 +21,7 @@ class UniqueJobTest extends TestCase protected function getEnvironmentSetUp($app) { $app['config']->set('database.default', 'testbench'); + $app['config']->set('database.connections.testbench', [ 'driver' => 'sqlite', 'database' => ':memory:', @@ -51,6 +52,7 @@ protected function tearDown(): void public function testUniqueJobsAreNotDispatched() { Bus::fake(); + UniqueTestJob::dispatch(); Bus::assertDispatched(UniqueTestJob::class); @@ -93,6 +95,7 @@ public function testLockIsReleasedForFailedJobs() public function testLockIsNotReleasedForJobRetries() { UniqueTestRetryJob::$handled = false; + dispatch($job = new UniqueTestRetryJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); @@ -142,11 +145,11 @@ public function testLockIsNotReleasedForJobReleases() protected function getLockKey($job) { - return 'unique:'.(is_string($job) ? $job : get_class($job)); + return 'laravel_unique_job:'.(is_string($job) ? $job : get_class($job)); } } -class UniqueTestJob implements ShouldQueue, UniqueJob +class UniqueTestJob implements ShouldQueue, ShouldBeUnique { use InteractsWithQueue, Queueable, Dispatchable; @@ -158,7 +161,7 @@ public function handle() } } -class UniqueTestFailJob implements ShouldQueue, UniqueJob +class UniqueTestFailJob implements ShouldQueue, ShouldBeUnique { use InteractsWithQueue, Queueable, Dispatchable;