This is version 0.1, and it relies on a protected property of Laravel's queue job class that a framework release could change. Try it on one job class before adding it everywhere.
Laravel checks ShouldBeUnique in one place: dispatch(). It takes the lock there, and if
another copy already holds it, the dispatch is quietly dropped. A job that reaches the queue any other way skips
that check.
That includes the first job of a Bus::chain(), jobs in a Bus::batch(),
Queue::push() and Queue::bulk(), and every queue:retry or press of
Horizon's retry button. A unique job queued that way runs even while another copy holds the lock, so two copies
run side by side. We cover the wider set of cases in
Laravel ShouldBeUnique: what breaks under real traffic.
A second check when the worker picks the job up
class RecalculateInvoice implements ShouldQueue, ShouldBeUnique
{
public function middleware(): array
{
return [new EnsureUniqueLock];
}
}
The middleware looks at the lock when the job is about to run. If this job holds it, or it is free, the job runs.
If another job holds it, this one is deleted without running and a UniqueJobSkipped event fires,
which you can log or count.
The hard part is telling "my lock" from "someone else's lock". Laravel takes the lock under a random owner token and then throws the token away. The package records it in the job's payload at push time, so the middleware has something to compare.
When in doubt, it runs the job
A job is only skipped on proof that another job holds its lock. If the cache store has no locks, Redis errors, or the job was queued before the package was installed, the job runs as it would have without it. In a chain, a skipped job ends the chain, as a discarded dispatch does. In a batch it counts as a success, so the batch does not wait forever.
Limits
-
If
uniqueForexpires while a job waits and a newer dispatch takes the lock, the older job is the one skipped. -
On Laravel 10 and early 11.x,
ShouldBeUniqueUntilProcessingjobs release their lock before middleware runs, so they are not protected there. PlainShouldBeUniquejobs are, on every supported version.
Every configuration key, and how to run the test suite, is in the README on GitHub. Bugs and questions go in its issues.