From 23dfad60f4a62d79794ff67ad02956dc280ceef4 Mon Sep 17 00:00:00 2001 From: Matthias Breddin Date: Tue, 10 Feb 2026 17:42:45 +0100 Subject: [PATCH] feat: make preventOverlapping lock TTL configurable Add optional `int $lockTtl` parameter to `preventOverlapping()` with a default of 30 seconds (preserving backward compatibility). The value is stored in a new `$lockTtl` property and used in `createLockObject()` instead of the previously hardcoded `$ttl = 30`. This allows users of TTL-based lock stores (e.g. RedisStore) to set a longer TTL, avoiding sporadic `LockConflictedException` caused by the probabilistic refresh mechanism not being able to reliably refresh a 30-second lock in time. --- src/Event.php | 10 ++++++---- tests/Unit/EventTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Event.php b/src/Event.php index 0f67428..f814641 100644 --- a/src/Event.php +++ b/src/Event.php @@ -157,6 +157,7 @@ class Event implements PingableInterface * Indicates if the command should not overlap itself. */ private bool $preventOverlapping = false; + private int $lockTtl = 30; /** @var ClockInterface */ private static $clock; private static ?ClosureSerializerInterface $closureSerializer = null; @@ -702,11 +703,14 @@ public function user($user) * that will be responsible for the locking. * * @param PersistingStoreInterface|object $store + * @param int $lockTtl TTL in seconds for the overlap-prevention lock (default: 30) * * @return $this */ - public function preventOverlapping(?object $store = null) + public function preventOverlapping(?object $store = null, int $lockTtl = 30) { + $this->lockTtl = $lockTtl; + if (null !== $store && !($store instanceof PersistingStoreInterface)) { $expectedClass = PersistingStoreInterface::class; $actualClass = $store::class; @@ -1101,10 +1105,8 @@ protected function createLockObject() $this->checkLockFactory(); if (null === $this->lock && null !== $this->lockFactory) { - $ttl = 30; - $this->lock = $this->lockFactory - ->createLock($this->lockKey(), $ttl); + ->createLock($this->lockKey(), $this->lockTtl); } return $this->lock; diff --git a/tests/Unit/EventTest.php b/tests/Unit/EventTest.php index 9299a9c..9d70862 100644 --- a/tests/Unit/EventTest.php +++ b/tests/Unit/EventTest.php @@ -665,6 +665,30 @@ public static function dateFromToProvider(): iterable ]; } + /** @test */ + public function prevent_overlapping_default_lock_ttl_is_30(): void + { + $event = $this->createEvent(); + $event->preventOverlapping(); + + $reflection = new \ReflectionProperty(Event::class, 'lockTtl'); + $reflection->setAccessible(true); + + self::assertSame(30, $reflection->getValue($event)); + } + + /** @test */ + public function prevent_overlapping_accepts_custom_lock_ttl(): void + { + $event = $this->createEvent(); + $event->preventOverlapping(null, 300); + + $reflection = new \ReflectionProperty(Event::class, 'lockTtl'); + $reflection->setAccessible(true); + + self::assertSame(300, $reflection->getValue($event)); + } + private function assertPreventOverlapping(?PersistingStoreInterface $store = null): void { $event = $this->createPreventOverlappingEvent($store);