[13.x] Fix unique queued listener locks not being released when the transaction is rolled back

Merged
SU
22d ago
+40-1

When a unique queued listener is dispatched after commit and the transaction rolls back, its unique lock is never released. The listener then can't be queued again for that unique ID until the lock expires, and with the default uniqueFor of 0 it never expires:

class SyncOrderToWarehouse implements ShouldQueueAfterCommit, ShouldBeUnique
{
    public function uniqueId(OrderPlaced $event): string
    {
        return $event->order->id;
    }
}

DB::transaction(function () use ($order) {
    event(new OrderPlaced($order));

    throw new Exception; // rolled back, the listener is never queued
});

event(new OrderPlaced($order)); // skipped, the lock from the rolled back dispatch is still held

#55420 releases the lock on rollback for unique jobs, but the check in registerRollbackCallbacksForJobsThatDispatchAfterCommit() only looks for ShouldBeUnique. Queued listeners are pushed as a CallQueuedListener, which tells you it's unique through shouldBeUnique() instead, so the rollback callback is never registered for them.

This checks for unique listeners the same way CallQueuedHandler::commandShouldBeUnique() already does. The test uses the array cache store, since with the database store the lock is part of the rolled back transaction.