-
Couldn't load subscription status.
- Fork 11.6k
Description
Laravel Version
12.20
PHP Version
8.4
Database Driver & Version
No response
Description
To keep the title brief, I called out the Skip middleware, but this actually applies to any middleware that doesn't return or call $next($job) for whatever reason.
Job middleware runs through a Pipeline in the CallQueuedHandler class:
framework/src/Illuminate/Queue/CallQueuedHandler.php
Lines 118 to 128 in 2c682e4
| return (new Pipeline($this->container))->send($command) | |
| ->through(array_merge(method_exists($command, 'middleware') ? $command->middleware() : [], $command->middleware ?? [])) | |
| ->then(function ($command) use ($job) { | |
| if ($command instanceof ShouldBeUniqueUntilProcessing) { | |
| $this->ensureUniqueJobLockIsReleased($command); | |
| } | |
| return $this->dispatcher->dispatchNow( | |
| $command, $this->resolveHandler($job, $command) | |
| ); | |
| }); |
When a job implements ShouldBeUniqueUntilProcessing, it is unlocked inside the then() callback after the Pipeline finishes executing.
In order for the final "then()" callback to actually called, every pipe in the pipeline must return/call $next(...). Failure to return the next pipe, or returning a different value (like false) will result in the then() callback being skipped.
Here is the Skip Middleware:
framework/src/Illuminate/Queue/Middleware/Skip.php
Lines 36 to 43 in 2c682e4
| public function handle(mixed $job, callable $next): mixed | |
| { | |
| if ($this->skip) { | |
| return false; | |
| } | |
| return $next($job); | |
| } |
Since it returns false when the condition/evaluation is false, the
then() callback in the Pipeline in the CallQueuedHandler doesn't execute and the job itself doesn't ever get unlocked.
Steps To Reproduce
Make a new Job that implements ShouldBeUnique. Have it use the Skip middleware and skip the job from running. The lock is never cleared. You will be unable to dispatch this job again until you manually clear the unique lock.
class SkippableJob implements ShouldBeUniqueUntilProcessing, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public int $a = 0,
) {}
public function handle()
{
ray('Job handled!');
}
public function middleware()
{
return [
Skip::when($this->a === 0),
];
}
}