Skip to content

Bug: ShouldBeUniqueUntilProcessing lock not cleared when using Skip middleware #56317

@TWithers

Description

@TWithers

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:

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:

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),
        ];
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions