Concurrency Limiting
Concurrency limiting prevents the same executable from running simultaneously. It uses Laravel's Cache::withoutOverlapping() under the hood, acquiring a lock before execute() runs and releasing it after. When the lock is already held, execution waits up to waitFor seconds to acquire it.
This works for both sync and queued execution.
Queue-only? Use middleware instead
Waiting for the lock blocks the worker process. For executables that only run on the queue, Laravel's WithoutOverlapping middleware should normally be a better fit since it releases the job back to the queue instead of occupying a worker. Use concurrency limiting when the executable may also run synchronously.
Attribute
For static lock keys, use the #[ConcurrencyLimit] attribute:
use Havn\Executable\Attributes\ConcurrencyLimit;
#[ConcurrencyLimit("process-orders")]
class ProcessOrder
{
use Executable;
public function execute(Order $order): void
{
/* ... */
}
}The attribute accepts the same options as Cache::withoutOverlapping(): key, lockFor, waitFor, and store.
#[ConcurrencyLimit(key: "process-orders", lockFor: 120, waitFor: 30, store: "redis")]
class ProcessOrder
{
/* ... */
}Method
When the lock key depends on execute-arguments, define a concurrencyLimit() method instead. It receives arguments by name, like other lifecycle methods:
class ProcessOrder
{
use Executable;
public function concurrencyLimit(Order $order): ConcurrencyLimit
{
return new ConcurrencyLimit(key: "process-order-{$order->id}");
}
public function execute(Order $order): void
{
/* ... */
}
}This locks per order instead of globally.
The method must return a ConcurrencyLimit instance. All the same options (lockFor, waitFor, store) are available as constructor arguments.
Priority
When both a concurrencyLimit() method and a #[ConcurrencyLimit] attribute are present, the method takes priority. The attribute is ignored.
