diff --git a/README.md b/README.md index 6b13059..4fe0eef 100644 --- a/README.md +++ b/README.md @@ -518,6 +518,11 @@ $task ->preventOverlapping($store); ``` +Optionally, you can specify a TTL (Time To Live) for the lock + +```php +$task->preventOverlapping($store, 60); // Lock expires after 60 seconds +``` ## Keeping the Output diff --git a/src/Event.php b/src/Event.php index 0f67428..8e7a052 100644 --- a/src/Event.php +++ b/src/Event.php @@ -701,11 +701,14 @@ public function user($user) * By default, the lock is acquired through file system locks. Alternatively, you can pass a symfony lock store * that will be responsible for the locking. * - * @param PersistingStoreInterface|object $store + * @param PersistingStoreInterface|object|null $store A symfony lock store + * @param int|null $ttl Time To Live of the lock in seconds * * @return $this + * + * @throws CrunzException */ - public function preventOverlapping(?object $store = null) + public function preventOverlapping(?object $store = null, ?int $ttl = 30) { if (null !== $store && !($store instanceof PersistingStoreInterface)) { $expectedClass = PersistingStoreInterface::class; @@ -721,8 +724,8 @@ public function preventOverlapping(?object $store = null) $this->lockFactory = new LockFactory($lockStore); // Skip the event if it's locked (processing) - $this->skip(function () { - $lock = $this->createLockObject(); + $this->skip(function () use ($ttl) { + $lock = $this->createLockObject($ttl); $lock->acquire(); return !$lock->isAcquired(); @@ -1008,7 +1011,8 @@ public function errorCallbacks() } /** - * If this event is prevented from overlapping, this method should be called regularly to refresh the lock. + * If this event is prevented from overlapping, this method should be called regularly to refresh the lock, + * UNLESS the lock store supports expiring (TTL), which means no refresh is needed. */ public function refreshLock(): void { @@ -1016,6 +1020,13 @@ public function refreshLock(): void return; } + // If the lock has remaining lifetime (i.e. the method returns a float and not NULL), that means the LockStore does support TTL [ 'MemcachedStore', 'MongoDbStore' , 'PdoStore', 'DoctrineDbalStore', 'RedisStore' ] + // @see https://symfony.com/doc/6.4/components/lock.html#available-stores for detailed information + $remainingLifetime = $this->lock->getRemainingLifetime(); + if (null !== $remainingLifetime) { + return; + } + $lock = $this->createLockObject(); $remainingLifetime = $lock->getRemainingLifetime(); @@ -1094,15 +1105,15 @@ public function everySixHours(): self /** * Get the symfony lock object for the task. * + * @param int|null $ttl Time To Live of the lock in seconds + * * @return Lock */ - protected function createLockObject() + protected function createLockObject(?int $ttl = 30) { $this->checkLockFactory(); if (null === $this->lock && null !== $this->lockFactory) { - $ttl = 30; - $this->lock = $this->lockFactory ->createLock($this->lockKey(), $ttl); }