Skip to content

Commit 4b785c6

Browse files
authored
Fix unique bug (#39302)
* fix translation bug and add test * refactor unique job queueing determination * Apply fixes from StyleCI Co-authored-by: Taylor Otwell <[email protected]>
1 parent cca0c5f commit 4b785c6

File tree

4 files changed

+146
-12
lines changed

4 files changed

+146
-12
lines changed

src/Illuminate/Bus/UniqueLock.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Illuminate\Bus;
4+
5+
use Illuminate\Contracts\Cache\Repository as Cache;
6+
7+
class UniqueLock
8+
{
9+
/**
10+
* The cache repository implementation.
11+
*
12+
* @var \Illuminate\Contracts\Cache\Repository
13+
*/
14+
protected $cache;
15+
16+
/**
17+
* Create a new unique lock manager instance.
18+
*
19+
* @param \Illuminate\Contracts\Cache\Repository $cache
20+
* @return void
21+
*/
22+
public function __construct(Cache $cache)
23+
{
24+
$this->cache = $cache;
25+
}
26+
27+
/**
28+
* Attempt to acquire a lock for the given job.
29+
*
30+
* @param mixed $job
31+
* @return bool
32+
*/
33+
public function acquire($job)
34+
{
35+
$uniqueId = method_exists($job, 'uniqueId')
36+
? $job->uniqueId()
37+
: ($job->uniqueId ?? '');
38+
39+
$cache = method_exists($job, 'uniqueVia')
40+
? $job->uniqueVia()
41+
: $this->cache;
42+
43+
return (bool) $cache->lock(
44+
$key = 'laravel_unique_job:'.get_class($job).$uniqueId,
45+
$job->uniqueFor ?? 0
46+
)->get();
47+
}
48+
}

src/Illuminate/Console/Scheduling/Schedule.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
use Closure;
66
use DateTimeInterface;
7+
use Illuminate\Bus\UniqueLock;
78
use Illuminate\Console\Application;
89
use Illuminate\Container\Container;
910
use Illuminate\Contracts\Bus\Dispatcher;
11+
use Illuminate\Contracts\Cache\Repository as Cache;
1012
use Illuminate\Contracts\Container\BindingResolutionException;
13+
use Illuminate\Contracts\Queue\ShouldBeUnique;
1114
use Illuminate\Contracts\Queue\ShouldQueue;
1215
use Illuminate\Queue\CallQueuedClosure;
1316
use Illuminate\Support\ProcessUtils;
@@ -168,6 +171,35 @@ protected function dispatchToQueue($job, $queue, $connection)
168171
$job = CallQueuedClosure::create($job);
169172
}
170173

174+
if ($job instanceof ShouldBeUnique) {
175+
return $this->dispatchUniqueJobToQueue($job, $queue, $connection);
176+
}
177+
178+
$this->getDispatcher()->dispatch(
179+
$job->onConnection($connection)->onQueue($queue)
180+
);
181+
}
182+
183+
/**
184+
* Dispatch the given unique job to the queue.
185+
*
186+
* @param object $job
187+
* @param string|null $queue
188+
* @param string|null $connection
189+
* @return void
190+
*
191+
* @throws \RuntimeException
192+
*/
193+
protected function dispatchUniqueJobToQueue($job, $queue, $connection)
194+
{
195+
if (! Container::getInstance()->bound(Cache::class)) {
196+
throw new RuntimeException('Cache driver not available. Scheduling unique jobs not supported.');
197+
}
198+
199+
if (! (new UniqueLock(Container::getInstance()->make(Cache::class)))->acquire($job)) {
200+
return;
201+
}
202+
171203
$this->getDispatcher()->dispatch(
172204
$job->onConnection($connection)->onQueue($queue)
173205
);

src/Illuminate/Foundation/Bus/PendingDispatch.php

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Foundation\Bus;
44

5+
use Illuminate\Bus\UniqueLock;
56
use Illuminate\Container\Container;
67
use Illuminate\Contracts\Bus\Dispatcher;
78
use Illuminate\Contracts\Cache\Repository as Cache;
@@ -159,18 +160,8 @@ protected function shouldDispatch()
159160
return true;
160161
}
161162

162-
$uniqueId = method_exists($this->job, 'uniqueId')
163-
? $this->job->uniqueId()
164-
: ($this->job->uniqueId ?? '');
165-
166-
$cache = method_exists($this->job, 'uniqueVia')
167-
? $this->job->uniqueVia()
168-
: Container::getInstance()->make(Cache::class);
169-
170-
return (bool) $cache->lock(
171-
$key = 'laravel_unique_job:'.get_class($this->job).$uniqueId,
172-
$this->job->uniqueFor ?? 0
173-
)->get();
163+
return (new UniqueLock(Container::getInstance()->make(Cache::class)))
164+
->acquire($this->job);
174165
}
175166

176167
/**
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Integration\Console;
4+
5+
use Illuminate\Bus\Queueable;
6+
use Illuminate\Console\Scheduling\Schedule;
7+
use Illuminate\Contracts\Queue\ShouldBeUnique;
8+
use Illuminate\Contracts\Queue\ShouldQueue;
9+
use Illuminate\Foundation\Bus\Dispatchable;
10+
use Illuminate\Queue\InteractsWithQueue;
11+
use Illuminate\Support\Facades\Queue;
12+
use Orchestra\Testbench\TestCase;
13+
14+
class UniqueJobSchedulingTest extends TestCase
15+
{
16+
public function testJobsPushedToQueue()
17+
{
18+
Queue::fake();
19+
$this->dispatch(
20+
TestJob::class,
21+
TestJob::class,
22+
TestJob::class,
23+
TestJob::class
24+
);
25+
26+
Queue::assertPushed(TestJob::class, 4);
27+
}
28+
29+
public function testUniqueJobsPushedToQueue()
30+
{
31+
Queue::fake();
32+
$this->dispatch(
33+
UniqueTestJob::class,
34+
UniqueTestJob::class,
35+
UniqueTestJob::class,
36+
UniqueTestJob::class
37+
);
38+
39+
Queue::assertPushed(UniqueTestJob::class, 1);
40+
}
41+
42+
private function dispatch(...$jobs)
43+
{
44+
/** @var \Illuminate\Console\Scheduling\Schedule $scheduler */
45+
$scheduler = $this->app->make(Schedule::class);
46+
foreach ($jobs as $job) {
47+
$scheduler->job($job)->name('')->everyMinute();
48+
}
49+
$events = $scheduler->events();
50+
foreach ($events as $event) {
51+
$event->run($this->app);
52+
}
53+
}
54+
}
55+
56+
class TestJob implements ShouldQueue
57+
{
58+
use InteractsWithQueue, Queueable, Dispatchable;
59+
}
60+
61+
class UniqueTestJob extends TestJob implements ShouldBeUnique
62+
{
63+
}

0 commit comments

Comments
 (0)