From 7ca19661325bc509e79bad62f52f6d637887bc90 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 11:26:54 +0800 Subject: [PATCH 1/8] lock add heartbeat param --- src/lock/src/Annotation/Blockable.php | 3 +- src/lock/src/Annotation/BlockableAspect.php | 2 +- src/lock/src/Annotation/Lock.php | 3 +- src/lock/src/Driver/AbstractLock.php | 38 ++++++++++++++++++- src/lock/src/Driver/CacheLock.php | 14 ++++++- src/lock/src/Driver/CoroutineLock.php | 13 ++++++- src/lock/src/Driver/DatabaseLock.php | 21 +++++++++- src/lock/src/Driver/FileSystemLock.php | 12 +++++- src/lock/src/Driver/RedisLock.php | 13 ++++++- src/lock/src/Functions.php | 4 +- .../RegisterPropertyHandlerListener.php | 3 +- src/lock/src/LockFactory.php | 3 +- 12 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/lock/src/Annotation/Blockable.php b/src/lock/src/Annotation/Blockable.php index 7cd030e22..51b2d74e3 100644 --- a/src/lock/src/Annotation/Blockable.php +++ b/src/lock/src/Annotation/Blockable.php @@ -22,7 +22,8 @@ public function __construct( public ?string $value = null, public int $seconds = 0, public int $ttl = 0, - public string $driver = 'default' + public string $driver = 'default', + public int $heartbeat = 0 ) { } } diff --git a/src/lock/src/Annotation/BlockableAspect.php b/src/lock/src/Annotation/BlockableAspect.php index eef17789e..00e37f598 100644 --- a/src/lock/src/Annotation/BlockableAspect.php +++ b/src/lock/src/Annotation/BlockableAspect.php @@ -39,7 +39,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $key = StringHelper::format($annotation->prefix, $arguments, $annotation->value); - return $this->lockFactory->make($key, $annotation->ttl, driver: $annotation->driver) + return $this->lockFactory->make($key, $annotation->ttl, driver: $annotation->driver, heartbeat: $annotation->heartbeat) ->block($annotation->seconds, fn () => $proceedingJoinPoint->process()); } } diff --git a/src/lock/src/Annotation/Lock.php b/src/lock/src/Annotation/Lock.php index 0e3a402ea..7340f21e9 100644 --- a/src/lock/src/Annotation/Lock.php +++ b/src/lock/src/Annotation/Lock.php @@ -21,7 +21,8 @@ public function __construct( public string $name, public int $seconds = 0, public ?string $owner = null, - public string $driver = 'default' + public string $driver = 'default', + public int $heartbeat = 0 ) { } } diff --git a/src/lock/src/Driver/AbstractLock.php b/src/lock/src/Driver/AbstractLock.php index 80fbc7708..b48879776 100644 --- a/src/lock/src/Driver/AbstractLock.php +++ b/src/lock/src/Driver/AbstractLock.php @@ -12,9 +12,13 @@ namespace FriendsOfHyperf\Lock\Driver; use FriendsOfHyperf\Lock\Exception\LockTimeoutException; +use Hyperf\Coordinator\Constants; +use Hyperf\Coordinator\CoordinatorManager; +use Hyperf\Coroutine\Coroutine; use Hyperf\Stringable\Str; use Hyperf\Support\Traits\InteractsWithTime; use Override; +use Throwable; use function Hyperf\Support\now; @@ -32,12 +36,15 @@ abstract class AbstractLock implements LockInterface */ protected int $sleepMilliseconds = 250; + protected int $heartbeat = 0; + /** * Create a new lock instance. */ - public function __construct(protected string $name, protected int $seconds, ?string $owner = null) + public function __construct(protected string $name, protected int $seconds, ?string $owner = null, int $heartbeat = 0) { $this->owner = $owner ?? Str::random(); + $this->heartbeat = $heartbeat; } /** @@ -59,10 +66,13 @@ public function get(?callable $callback = null) { $result = $this->acquire(); + $result && $this->heartbeat(); + if ($result && is_callable($callback)) { try { return $callback(); } finally { + $this->heartbeat = 0; $this->release(); } } @@ -90,10 +100,12 @@ public function block(int $seconds, ?callable $callback = null) usleep($this->sleepMilliseconds * 1000); } + $this->heartbeat(); if (is_callable($callback)) { try { return $callback(); } finally { + $this->heartbeat = 0; $this->release(); } } @@ -131,6 +143,8 @@ public function isOwnedBy($owner): bool return $this->getCurrentOwner() === $owner; } + abstract protected function delayExpiration(): bool; + /** * Returns the owner value written into the driver for this lock. */ @@ -143,4 +157,26 @@ protected function isOwnedByCurrentProcess(): bool { return $this->isOwnedBy($this->owner); } + + private function heartbeat(): void + { + if ($this->heartbeat == 0 || $this->seconds <= 0 || ! Coroutine::inCoroutine()) { + return; + } + Coroutine::create(function () { + while (true) { + if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield($this->heartbeat)) { + break; + } + if ($this->heartbeat == 0) { + return; + } + try { + $this->delayExpiration(); + } catch (Throwable $throwable) { + // print log + } + } + }); + } } diff --git a/src/lock/src/Driver/CacheLock.php b/src/lock/src/Driver/CacheLock.php index 8c6bfbd11..e1f517734 100644 --- a/src/lock/src/Driver/CacheLock.php +++ b/src/lock/src/Driver/CacheLock.php @@ -26,9 +26,9 @@ class CacheLock extends AbstractLock /** * Create a new lock instance. */ - public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = []) + public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = [], int $heartbeat = 0) { - parent::__construct($name, $seconds, $owner); + parent::__construct($name, $seconds, $owner, $heartbeat); $container = ApplicationContext::getContainer(); $cacheManager = $container->get(CacheManager::class); @@ -49,6 +49,16 @@ public function acquire(): bool return $this->store->set($this->name, $this->owner, $this->seconds); } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0){ + return $this->store->set($this->name, $this->owner, $this->seconds); + } + return true; + } + + /** * Release the lock. */ diff --git a/src/lock/src/Driver/CoroutineLock.php b/src/lock/src/Driver/CoroutineLock.php index 099b134d1..553159474 100644 --- a/src/lock/src/Driver/CoroutineLock.php +++ b/src/lock/src/Driver/CoroutineLock.php @@ -43,12 +43,13 @@ public function __construct( string $name, int $seconds, ?string $owner = null, - array $constructor = [] + array $constructor = [], + int $heartbeat = 0 ) { $constructor = array_merge(['prefix' => ''], $constructor); $name = $constructor['prefix'] . $name; - parent::__construct($name, $seconds, $owner); + parent::__construct($name, $seconds, $owner, $heartbeat); self::$owners ??= new WeakMap(); self::$timers ??= new WeakMap(); @@ -85,6 +86,14 @@ public function acquire(): bool return true; } + #[Override] + protected function delayExpiration(): bool + { + // Not supported + return false; + } + + /** * Release the lock. */ diff --git a/src/lock/src/Driver/DatabaseLock.php b/src/lock/src/Driver/DatabaseLock.php index 90012be7b..e126b394c 100644 --- a/src/lock/src/Driver/DatabaseLock.php +++ b/src/lock/src/Driver/DatabaseLock.php @@ -28,9 +28,9 @@ class DatabaseLock extends AbstractLock /** * Create a new lock instance. */ - public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = []) + public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = [], int $heartbeat = 0) { - parent::__construct($name, $seconds, $owner); + parent::__construct($name, $seconds, $owner, $heartbeat); $constructor = array_merge(['pool' => 'default', 'table' => 'locks', 'prefix' => ''], $constructor); if ($constructor['prefix']) { @@ -71,6 +71,23 @@ public function acquire(): bool return $acquired; } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0){ + $updated = $this->connection->table($this->table) + ->where('key', $this->name) + ->where(fn ($query) => $query->where('owner', $this->owner)->orWhere('expiration', '<=', time())) + ->update([ + 'owner' => $this->owner, + 'expiration' => $this->expiresAt(), + ]); + + return $updated >= 1; + } + return true; + } + /** * Release the lock. */ diff --git a/src/lock/src/Driver/FileSystemLock.php b/src/lock/src/Driver/FileSystemLock.php index de703ec8f..9ad2c2071 100644 --- a/src/lock/src/Driver/FileSystemLock.php +++ b/src/lock/src/Driver/FileSystemLock.php @@ -26,9 +26,9 @@ class FileSystemLock extends AbstractLock /** * Create a new lock instance. */ - public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = []) + public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = [], int $heartbeat = 0) { - parent::__construct($name, $seconds, $owner); + parent::__construct($name, $seconds, $owner, $heartbeat); $constructor = array_merge(['config' => ['prefix' => 'lock:']], $constructor); $this->store = make(FileSystemDriver::class, $constructor); @@ -46,6 +46,14 @@ public function acquire(): bool return $this->store->set($this->name, $this->owner, $this->seconds) == true; } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0){ + return $this->store->set($this->name, $this->owner, $this->seconds); + } + return true; + } /** * Release the lock. diff --git a/src/lock/src/Driver/RedisLock.php b/src/lock/src/Driver/RedisLock.php index 11e399577..04806cac7 100644 --- a/src/lock/src/Driver/RedisLock.php +++ b/src/lock/src/Driver/RedisLock.php @@ -28,9 +28,9 @@ class RedisLock extends AbstractLock /** * Create a new lock instance. */ - public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = []) + public function __construct(string $name, int $seconds, ?string $owner = null, array $constructor = [], int $heartbeat = 0) { - parent::__construct($name, $seconds, $owner); + parent::__construct($name, $seconds, $owner, $heartbeat); $constructor = array_merge(['pool' => 'default', 'prefix' => ''], $constructor); if ($constructor['prefix']) { @@ -52,6 +52,15 @@ public function acquire(): bool return $this->store->setNX($this->name, $this->owner) === true; } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0) { + return $this->store->set($this->name, $this->owner, ['EX' => $this->seconds]); + } + return true; + } + /** * Release the lock. */ diff --git a/src/lock/src/Functions.php b/src/lock/src/Functions.php index 9e69444cc..de993b3c8 100644 --- a/src/lock/src/Functions.php +++ b/src/lock/src/Functions.php @@ -17,7 +17,7 @@ /** * @return ($name is null ? LockFactory : LockInterface) */ -function lock(?string $name = null, int $seconds = 0, ?string $owner = null, string $driver = 'default'): LockFactory|LockInterface +function lock(?string $name = null, int $seconds = 0, ?string $owner = null, string $driver = 'default', int $heartbeat = 0): LockFactory|LockInterface { $factory = ApplicationContext::getContainer()->get(LockFactory::class); @@ -25,5 +25,5 @@ function lock(?string $name = null, int $seconds = 0, ?string $owner = null, str return $factory; } - return $factory->make($name, $seconds, $owner, $driver); + return $factory->make($name, $seconds, $owner, $driver, $heartbeat); } diff --git a/src/lock/src/Listener/RegisterPropertyHandlerListener.php b/src/lock/src/Listener/RegisterPropertyHandlerListener.php index 0ff5372a2..ce9b902ee 100644 --- a/src/lock/src/Listener/RegisterPropertyHandlerListener.php +++ b/src/lock/src/Listener/RegisterPropertyHandlerListener.php @@ -48,8 +48,9 @@ public function process(object $event): void $seconds = (int) $annotation->seconds; $owner = $annotation->owner; $driver = $annotation->driver; + $heartbeat = $annotation->heartbeat; - $reflectionProperty->setValue($object, $this->lockFactory->make($name, $seconds, $owner, $driver)); + $reflectionProperty->setValue($object, $this->lockFactory->make($name, $seconds, $owner, $driver, $heartbeat)); } }); } diff --git a/src/lock/src/LockFactory.php b/src/lock/src/LockFactory.php index a7f13aa46..3c866c234 100644 --- a/src/lock/src/LockFactory.php +++ b/src/lock/src/LockFactory.php @@ -27,7 +27,7 @@ public function __construct(private ConfigInterface $config) /** * Get a lock instance. */ - public function make(string $name, int $seconds = 0, ?string $owner = null, string $driver = 'default'): LockInterface + public function make(string $name, int $seconds = 0, ?string $owner = null, string $driver = 'default', int $heartbeat = 0): LockInterface { $driver = $driver ?: 'default'; @@ -45,6 +45,7 @@ public function make(string $name, int $seconds = 0, ?string $owner = null, stri 'seconds' => $seconds, 'owner' => $owner, 'constructor' => $constructor, + 'heartbeat' => $heartbeat, ]); } } From fa1d198c3fce260d8a99160dcd0e28e252671dc3 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:59:20 +0800 Subject: [PATCH 2/8] refactor: update delayExpiration method in lock drivers for consistency --- src/lock/src/Driver/CacheLock.php | 19 +++++++------- src/lock/src/Driver/CoroutineLock.php | 15 ++++++------ src/lock/src/Driver/DatabaseLock.php | 34 +++++++++++++------------- src/lock/src/Driver/FileSystemLock.php | 17 +++++++------ src/lock/src/Driver/RedisLock.php | 18 +++++++------- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/lock/src/Driver/CacheLock.php b/src/lock/src/Driver/CacheLock.php index e1f517734..a7eac3b70 100644 --- a/src/lock/src/Driver/CacheLock.php +++ b/src/lock/src/Driver/CacheLock.php @@ -49,16 +49,6 @@ public function acquire(): bool return $this->store->set($this->name, $this->owner, $this->seconds); } - #[Override] - protected function delayExpiration(): bool - { - if ($this->seconds > 0){ - return $this->store->set($this->name, $this->owner, $this->seconds); - } - return true; - } - - /** * Release the lock. */ @@ -81,6 +71,15 @@ public function forceRelease(): void $this->store->delete($this->name); } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0) { + return $this->store->set($this->name, $this->owner, $this->seconds); + } + return true; + } + /** * Returns the owner value written into the driver for this lock. * @return string diff --git a/src/lock/src/Driver/CoroutineLock.php b/src/lock/src/Driver/CoroutineLock.php index 553159474..feebcb05c 100644 --- a/src/lock/src/Driver/CoroutineLock.php +++ b/src/lock/src/Driver/CoroutineLock.php @@ -86,14 +86,6 @@ public function acquire(): bool return true; } - #[Override] - protected function delayExpiration(): bool - { - // Not supported - return false; - } - - /** * Release the lock. */ @@ -122,6 +114,13 @@ public function forceRelease(): void $chan->close(); } + #[Override] + protected function delayExpiration(): bool + { + // Not supported + return false; + } + /** * Returns the owner value written into the driver for this lock. * @return string diff --git a/src/lock/src/Driver/DatabaseLock.php b/src/lock/src/Driver/DatabaseLock.php index e126b394c..d1e995310 100644 --- a/src/lock/src/Driver/DatabaseLock.php +++ b/src/lock/src/Driver/DatabaseLock.php @@ -71,23 +71,6 @@ public function acquire(): bool return $acquired; } - #[Override] - protected function delayExpiration(): bool - { - if ($this->seconds > 0){ - $updated = $this->connection->table($this->table) - ->where('key', $this->name) - ->where(fn ($query) => $query->where('owner', $this->owner)->orWhere('expiration', '<=', time())) - ->update([ - 'owner' => $this->owner, - 'expiration' => $this->expiresAt(), - ]); - - return $updated >= 1; - } - return true; - } - /** * Release the lock. */ @@ -117,6 +100,23 @@ public function forceRelease(): void ->delete(); } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0) { + $updated = $this->connection->table($this->table) + ->where('key', $this->name) + ->where(fn ($query) => $query->where('owner', $this->owner)->orWhere('expiration', '<=', time())) + ->update([ + 'owner' => $this->owner, + 'expiration' => $this->expiresAt(), + ]); + + return $updated >= 1; + } + return true; + } + /** * Get the UNIX timestamp indicating when the lock should expire. */ diff --git a/src/lock/src/Driver/FileSystemLock.php b/src/lock/src/Driver/FileSystemLock.php index 9ad2c2071..663793d11 100644 --- a/src/lock/src/Driver/FileSystemLock.php +++ b/src/lock/src/Driver/FileSystemLock.php @@ -46,14 +46,6 @@ public function acquire(): bool return $this->store->set($this->name, $this->owner, $this->seconds) == true; } - #[Override] - protected function delayExpiration(): bool - { - if ($this->seconds > 0){ - return $this->store->set($this->name, $this->owner, $this->seconds); - } - return true; - } /** * Release the lock. @@ -77,6 +69,15 @@ public function forceRelease(): void $this->store->delete($this->name); } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0) { + return $this->store->set($this->name, $this->owner, $this->seconds); + } + return true; + } + /** * Returns the owner value written into the driver for this lock. * @return string diff --git a/src/lock/src/Driver/RedisLock.php b/src/lock/src/Driver/RedisLock.php index 04806cac7..9d5efe541 100644 --- a/src/lock/src/Driver/RedisLock.php +++ b/src/lock/src/Driver/RedisLock.php @@ -52,15 +52,6 @@ public function acquire(): bool return $this->store->setNX($this->name, $this->owner) === true; } - #[Override] - protected function delayExpiration(): bool - { - if ($this->seconds > 0) { - return $this->store->set($this->name, $this->owner, ['EX' => $this->seconds]); - } - return true; - } - /** * Release the lock. */ @@ -79,6 +70,15 @@ public function forceRelease(): void $this->store->del($this->name); } + #[Override] + protected function delayExpiration(): bool + { + if ($this->seconds > 0) { + return $this->store->set($this->name, $this->owner, ['EX' => $this->seconds]); + } + return true; + } + /** * Returns the owner value written into the driver for this lock. * @return string From 0640e20f2cc9687b3a9717234cf9c5546eaae2b5 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 13:52:29 +0800 Subject: [PATCH 3/8] lock add stopHeartbeat method --- src/lock/src/Driver/AbstractLock.php | 15 ++++++++------- src/lock/src/Driver/CacheLock.php | 4 +++- src/lock/src/Driver/DatabaseLock.php | 4 +++- src/lock/src/Driver/FileSystemLock.php | 4 +++- src/lock/src/Driver/RedisLock.php | 4 +++- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/lock/src/Driver/AbstractLock.php b/src/lock/src/Driver/AbstractLock.php index b48879776..8f07a819f 100644 --- a/src/lock/src/Driver/AbstractLock.php +++ b/src/lock/src/Driver/AbstractLock.php @@ -66,13 +66,10 @@ public function get(?callable $callback = null) { $result = $this->acquire(); - $result && $this->heartbeat(); - if ($result && is_callable($callback)) { try { return $callback(); } finally { - $this->heartbeat = 0; $this->release(); } } @@ -100,12 +97,10 @@ public function block(int $seconds, ?callable $callback = null) usleep($this->sleepMilliseconds * 1000); } - $this->heartbeat(); if (is_callable($callback)) { try { return $callback(); } finally { - $this->heartbeat = 0; $this->release(); } } @@ -158,10 +153,10 @@ protected function isOwnedByCurrentProcess(): bool return $this->isOwnedBy($this->owner); } - private function heartbeat(): void + protected function heartbeat(): bool { if ($this->heartbeat == 0 || $this->seconds <= 0 || ! Coroutine::inCoroutine()) { - return; + return false; } Coroutine::create(function () { while (true) { @@ -178,5 +173,11 @@ private function heartbeat(): void } } }); + return true; + } + + protected function stopHeartbeat(): void + { + $this->heartbeat = 0; } } diff --git a/src/lock/src/Driver/CacheLock.php b/src/lock/src/Driver/CacheLock.php index a7eac3b70..dd5d43efe 100644 --- a/src/lock/src/Driver/CacheLock.php +++ b/src/lock/src/Driver/CacheLock.php @@ -46,7 +46,7 @@ public function acquire(): bool return false; } - return $this->store->set($this->name, $this->owner, $this->seconds); + return $this->store->set($this->name, $this->owner, $this->seconds) && $this->heartbeat(); } /** @@ -56,6 +56,7 @@ public function acquire(): bool public function release(): bool { if ($this->isOwnedByCurrentProcess()) { + $this->stopHeartbeat(); return $this->store->delete($this->name); } @@ -68,6 +69,7 @@ public function release(): bool #[Override] public function forceRelease(): void { + $this->stopHeartbeat(); $this->store->delete($this->name); } diff --git a/src/lock/src/Driver/DatabaseLock.php b/src/lock/src/Driver/DatabaseLock.php index d1e995310..80e734e0a 100644 --- a/src/lock/src/Driver/DatabaseLock.php +++ b/src/lock/src/Driver/DatabaseLock.php @@ -67,7 +67,7 @@ public function acquire(): bool $acquired = $updated >= 1; } - + $acquired && $this->heartbeat(); return $acquired; } @@ -78,6 +78,7 @@ public function acquire(): bool public function release(): bool { if ($this->isOwnedByCurrentProcess()) { + $this->stopHeartbeat(); $this->connection->table($this->table) ->where('key', $this->name) ->where('owner', $this->owner) @@ -95,6 +96,7 @@ public function release(): bool #[Override] public function forceRelease(): void { + $this->stopHeartbeat(); $this->connection->table($this->table) ->where('key', $this->name) ->delete(); diff --git a/src/lock/src/Driver/FileSystemLock.php b/src/lock/src/Driver/FileSystemLock.php index 663793d11..f6a4524a2 100644 --- a/src/lock/src/Driver/FileSystemLock.php +++ b/src/lock/src/Driver/FileSystemLock.php @@ -44,7 +44,7 @@ public function acquire(): bool return false; } - return $this->store->set($this->name, $this->owner, $this->seconds) == true; + return $this->store->set($this->name, $this->owner, $this->seconds) == true && $this->heartbeat(); } /** @@ -54,6 +54,7 @@ public function acquire(): bool public function release(): bool { if ($this->isOwnedByCurrentProcess()) { + $this->stopHeartbeat(); return $this->store->delete($this->name); } @@ -66,6 +67,7 @@ public function release(): bool #[Override] public function forceRelease(): void { + $this->stopHeartbeat(); $this->store->delete($this->name); } diff --git a/src/lock/src/Driver/RedisLock.php b/src/lock/src/Driver/RedisLock.php index 9d5efe541..7ddc15fce 100644 --- a/src/lock/src/Driver/RedisLock.php +++ b/src/lock/src/Driver/RedisLock.php @@ -46,7 +46,7 @@ public function __construct(string $name, int $seconds, ?string $owner = null, a public function acquire(): bool { if ($this->seconds > 0) { - return $this->store->set($this->name, $this->owner, ['NX', 'EX' => $this->seconds]) == true; + return $this->store->set($this->name, $this->owner, ['NX', 'EX' => $this->seconds]) == true && $this->heartbeat(); } return $this->store->setNX($this->name, $this->owner) === true; @@ -58,6 +58,7 @@ public function acquire(): bool #[Override] public function release(): bool { + $this->stopHeartbeat(); return (bool) $this->store->eval(LuaScripts::releaseLock(), [$this->name, $this->owner], 1); } @@ -67,6 +68,7 @@ public function release(): bool #[Override] public function forceRelease(): void { + $this->stopHeartbeat(); $this->store->del($this->name); } From 81a651fc5c133dfe13990aa4300b9550d1220f18 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 14:39:03 +0800 Subject: [PATCH 4/8] heartbeat and seconds judgment --- src/lock/src/Driver/AbstractLock.php | 7 +++++++ src/lock/src/Exception/HeartbeatException.php | 18 ++++++++++++++++++ tests/Lock/AbstractLockTest.php | 5 +++++ 3 files changed, 30 insertions(+) create mode 100644 src/lock/src/Exception/HeartbeatException.php diff --git a/src/lock/src/Driver/AbstractLock.php b/src/lock/src/Driver/AbstractLock.php index 8f07a819f..5f08c8a03 100644 --- a/src/lock/src/Driver/AbstractLock.php +++ b/src/lock/src/Driver/AbstractLock.php @@ -11,6 +11,7 @@ namespace FriendsOfHyperf\Lock\Driver; +use FriendsOfHyperf\Lock\Exception\HeartbeatException; use FriendsOfHyperf\Lock\Exception\LockTimeoutException; use Hyperf\Coordinator\Constants; use Hyperf\Coordinator\CoordinatorManager; @@ -45,6 +46,9 @@ public function __construct(protected string $name, protected int $seconds, ?str { $this->owner = $owner ?? Str::random(); $this->heartbeat = $heartbeat; + if ($seconds > 0 && $heartbeat > 0 && $seconds <= $heartbeat) { + throw new HeartbeatException('Heartbeat must be less than lock seconds.'); + } } /** @@ -166,6 +170,9 @@ protected function heartbeat(): bool if ($this->heartbeat == 0) { return; } + if (! $this->isOwnedByCurrentProcess()) { + return; + } try { $this->delayExpiration(); } catch (Throwable $throwable) { diff --git a/src/lock/src/Exception/HeartbeatException.php b/src/lock/src/Exception/HeartbeatException.php new file mode 100644 index 000000000..8ecdd8932 --- /dev/null +++ b/src/lock/src/Exception/HeartbeatException.php @@ -0,0 +1,18 @@ +currentOwner; } + + protected function delayExpiration(): bool + { + return true; + } } test('abstract lock generates random owner when none provided', function () { From 3d82a41baba27012b21b3037590d08908829e6c6 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 14:53:07 +0800 Subject: [PATCH 5/8] heartbeat judgment --- src/lock/src/Driver/AbstractLock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lock/src/Driver/AbstractLock.php b/src/lock/src/Driver/AbstractLock.php index 5f08c8a03..4abb0da78 100644 --- a/src/lock/src/Driver/AbstractLock.php +++ b/src/lock/src/Driver/AbstractLock.php @@ -159,7 +159,7 @@ protected function isOwnedByCurrentProcess(): bool protected function heartbeat(): bool { - if ($this->heartbeat == 0 || $this->seconds <= 0 || ! Coroutine::inCoroutine()) { + if ($this->heartbeat <= 0 || $this->seconds <= 0 || ! Coroutine::inCoroutine()) { return false; } Coroutine::create(function () { From cb65045c28cb23e24a52f1bdd288ca19d75c4dfc Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 15:05:52 +0800 Subject: [PATCH 6/8] heartbeat judgment 2 --- src/lock/src/Driver/AbstractLock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lock/src/Driver/AbstractLock.php b/src/lock/src/Driver/AbstractLock.php index 4abb0da78..4617f317a 100644 --- a/src/lock/src/Driver/AbstractLock.php +++ b/src/lock/src/Driver/AbstractLock.php @@ -160,7 +160,7 @@ protected function isOwnedByCurrentProcess(): bool protected function heartbeat(): bool { if ($this->heartbeat <= 0 || $this->seconds <= 0 || ! Coroutine::inCoroutine()) { - return false; + return true; } Coroutine::create(function () { while (true) { From 022739cdfbc71825a9adb966ca491884b4c7bbbd Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 15:11:33 +0800 Subject: [PATCH 7/8] lock test --- tests/Lock/FunctionsTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Lock/FunctionsTest.php b/tests/Lock/FunctionsTest.php index f6ff6d7c7..e1a4b8642 100644 --- a/tests/Lock/FunctionsTest.php +++ b/tests/Lock/FunctionsTest.php @@ -25,7 +25,7 @@ test('lock function returns lock instance when name provided', function () { $lockInstance = $this->mock(LockInterface::class); $factory = $this->mock(LockFactory::class, function ($mock) use ($lockInstance) { - $mock->shouldReceive('make')->with('foo', 0, null, 'default')->andReturn($lockInstance); + $mock->shouldReceive('make')->with('foo', 0, null, 'default', 0)->andReturn($lockInstance); }); $this->instance(LockFactory::class, $factory); @@ -37,7 +37,7 @@ test('lock function passes all parameters correctly', function () { $lockInstance = $this->mock(LockInterface::class); $factory = $this->mock(LockFactory::class, function ($mock) use ($lockInstance) { - $mock->shouldReceive('make')->with('mylock', 60, 'owner123', 'redis')->andReturn($lockInstance); + $mock->shouldReceive('make')->with('mylock', 60, 'owner123', 'redis', 0)->andReturn($lockInstance); }); $this->instance(LockFactory::class, $factory); @@ -49,7 +49,7 @@ test('lock function uses default values correctly', function () { $lockInstance = $this->mock(LockInterface::class); $factory = $this->mock(LockFactory::class, function ($mock) use ($lockInstance) { - $mock->shouldReceive('make')->with('test', 30, null, 'default')->andReturn($lockInstance); + $mock->shouldReceive('make')->with('test', 30, null, 'default', 0)->andReturn($lockInstance); }); $this->instance(LockFactory::class, $factory); From 6d03a8fde95451042132a6d9b982123cf3140ab3 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 24 Nov 2025 15:53:11 +0800 Subject: [PATCH 8/8] add log --- src/lock/src/Driver/AbstractLock.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lock/src/Driver/AbstractLock.php b/src/lock/src/Driver/AbstractLock.php index 4617f317a..38c3d53fc 100644 --- a/src/lock/src/Driver/AbstractLock.php +++ b/src/lock/src/Driver/AbstractLock.php @@ -13,6 +13,8 @@ use FriendsOfHyperf\Lock\Exception\HeartbeatException; use FriendsOfHyperf\Lock\Exception\LockTimeoutException; +use Hyperf\Context\ApplicationContext; +use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\Coordinator\Constants; use Hyperf\Coordinator\CoordinatorManager; use Hyperf\Coroutine\Coroutine; @@ -176,7 +178,7 @@ protected function heartbeat(): bool try { $this->delayExpiration(); } catch (Throwable $throwable) { - // print log + ApplicationContext::getContainer()->get(StdoutLoggerInterface::class)?->warning($throwable); } } });