Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/lock/src/Annotation/Blockable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
}
}
2 changes: 1 addition & 1 deletion src/lock/src/Annotation/BlockableAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
3 changes: 2 additions & 1 deletion src/lock/src/Annotation/Lock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
}
}
48 changes: 47 additions & 1 deletion src/lock/src/Driver/AbstractLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@

namespace FriendsOfHyperf\Lock\Driver;

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;
use Hyperf\Stringable\Str;
use Hyperf\Support\Traits\InteractsWithTime;
use Override;
use Throwable;

use function Hyperf\Support\now;

Expand All @@ -32,12 +39,18 @@ 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;
if ($seconds > 0 && $heartbeat > 0 && $seconds <= $heartbeat) {
throw new HeartbeatException('Heartbeat must be less than lock seconds.');
}
}

/**
Expand Down Expand Up @@ -131,6 +144,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.
*/
Expand All @@ -143,4 +158,35 @@ protected function isOwnedByCurrentProcess(): bool
{
return $this->isOwnedBy($this->owner);
}

protected function heartbeat(): bool
{
if ($this->heartbeat <= 0 || $this->seconds <= 0 || ! Coroutine::inCoroutine()) {
return true;
}
Coroutine::create(function () {
while (true) {
if (CoordinatorManager::until(Constants::WORKER_EXIT)->yield($this->heartbeat)) {
break;
}
if ($this->heartbeat == 0) {
return;
}
if (! $this->isOwnedByCurrentProcess()) {
return;
}
try {
$this->delayExpiration();
} catch (Throwable $throwable) {
ApplicationContext::getContainer()->get(StdoutLoggerInterface::class)?->warning($throwable);
}
}
});
return true;
}
Comment on lines +162 to +186
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

关键缺陷:心跳方法未被调用,功能无法生效

heartbeat() 方法的实现逻辑本身是正确的:

  • 守卫条件(第 164 行)正确检查了有效性
  • 所有权检查(第 175-177 行)防止了旧实例干扰新所有者
  • 异常处理和日志记录(第 181 行)已正确实现

但存在关键问题:此方法在整个抽象类中从未被调用

查看 get() 方法(第 71-84 行)和 block() 方法(第 91-115 行),在成功获取锁后都没有调用 heartbeat(),导致心跳机制根本不会启动。

建议在以下位置之一调用 heartbeat()

  1. get() 方法中,第 73 行 $result = $this->acquire(); 之后,当 $result 为 true 时调用
  2. block() 方法中,第 104 行成功获取锁后立即调用
  3. 或者在各个具体驱动的 acquire() 方法成功返回前调用

示例修改(方案 1):

 public function get(?callable $callback = null)
 {
     $result = $this->acquire();
+    if ($result) {
+        $this->heartbeat();
+    }

     if ($result && is_callable($callback)) {

示例修改(方案 2):

     while (! $this->acquire()) {
         $now = ((int) now()->format('Uu')) / 1000;

         if (($now + $this->sleepMilliseconds - $milliseconds) >= $starting) {
             throw new LockTimeoutException();
         }

         usleep($this->sleepMilliseconds * 1000);
     }
+    $this->heartbeat();

     if (is_callable($callback)) {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/lock/src/Driver/AbstractLock.php around lines 162-186: the heartbeat()
method is never invoked so the background renewal loop never starts; modify the
class to call $this->heartbeat() immediately after a successful lock acquisition
— e.g. in get() right after $result = $this->acquire(); when $result is true,
and likewise in block() immediately after a successful acquire/when the lock is
obtained (or ensure concrete drivers invoke heartbeat() before returning success
from acquire()); keep the existing guards and ownership checks as-is and ensure
the call is only made when acquisition succeeds.


protected function stopHeartbeat(): void
{
$this->heartbeat = 0;
}
Comment on lines +188 to +191
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺少 stopHeartbeat() 调用,心跳协程可能无法及时停止

stopHeartbeat() 方法的实现正确,但在可见代码中从未被调用:

  • get() 方法的 finally 块(第 79 行)只调用了 release()
  • block() 方法的 finally 块(第 110 行)也只调用了 release()

根据之前的评审意见和最佳实践,应在释放锁时停止心跳协程,以防止资源泄漏。

建议在 finally 块中释放锁之前调用 stopHeartbeat()

 if ($result && is_callable($callback)) {
     try {
         return $callback();
     } finally {
+        $this->stopHeartbeat();
         $this->release();
     }
 }

同样的修改也应应用于 block() 方法的 finally 块(第 110 行)。

此外,release()forceRelease() 方法(由具体驱动实现)中也应该调用 stopHeartbeat(),以确保无论哪种释放路径都能停止心跳。

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/lock/src/Driver/AbstractLock.php around lines 188-191: stopHeartbeat() is
implemented but never invoked; update the finally blocks in get() (around line
79) and block() (around line 110) to call $this->stopHeartbeat() before calling
$this->release(); also update the concrete driver implementations of release()
and forceRelease() (or their overrides) to invoke $this->stopHeartbeat() as part
of their release path so the heartbeat coroutine is always stopped regardless of
which release method is used.

}
17 changes: 14 additions & 3 deletions src/lock/src/Driver/CacheLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
}

/**
Expand All @@ -56,6 +56,7 @@ public function acquire(): bool
public function release(): bool
{
if ($this->isOwnedByCurrentProcess()) {
$this->stopHeartbeat();
return $this->store->delete($this->name);
}

Expand All @@ -68,9 +69,19 @@ public function release(): bool
#[Override]
public function forceRelease(): void
{
$this->stopHeartbeat();
$this->store->delete($this->name);
}

#[Override]
protected function delayExpiration(): bool
{
if ($this->seconds > 0) {
return $this->store->set($this->name, $this->owner, $this->seconds);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不对吧,一直在续期,正常到期了,其他也可以抢锁才对

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

防止任务还没有完成 , 就释放了锁

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

设计如此啊,如果不想提前释放,那就不设定时间就好了

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有了心跳处理, seconds参数 重要性, 降低了;

seconds 应该大于 heartbeat

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那这个心跳就没有意义了

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有了心跳处理, seconds参数 重要性, 降低了;

seconds 应该大于 heartbeat

我感觉你的需求是mutex,不是lock

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是lock, 动态增加锁的时间; 保证相同任务执行的唯一性

}
return true;
}
Comment on lines +76 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

delayExpiration() 缺少所有权验证。

delayExpiration() 方法直接调用 $this->store->set() 来刷新过期时间,但没有验证当前进程是否拥有该锁。这与 release() 方法(第 58 行先检查 isOwnedByCurrentProcess() 再删除)形成对比。如果心跳协程在锁已被其他进程获取后继续运行,可能会导致进程延长不属于自己的锁的过期时间。

建议在 delayExpiration() 方法中添加所有权检查:

protected function delayExpiration(): bool
{
    if ($this->isOwnedByCurrentProcess() && $this->seconds > 0) {
        return $this->store->set($this->name, $this->owner, $this->seconds);
    }
    return false;
}
🤖 Prompt for AI Agents
In src/lock/src/Driver/CacheLock.php around lines 76 to 83, delayExpiration()
currently refreshes the TTL unconditionally which can extend locks owned by
other processes; modify it to first verify ownership with
isOwnedByCurrentProcess() and only call $this->store->set($this->name,
$this->owner, $this->seconds) when both the lock is owned by the current process
and $this->seconds > 0, otherwise return false to avoid extending foreign locks.


/**
* Returns the owner value written into the driver for this lock.
* @return string
Expand Down
12 changes: 10 additions & 2 deletions src/lock/src/Driver/CoroutineLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -113,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
Expand Down
25 changes: 22 additions & 3 deletions src/lock/src/Driver/DatabaseLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']) {
Expand Down Expand Up @@ -67,7 +67,7 @@ public function acquire(): bool

$acquired = $updated >= 1;
}

$acquired && $this->heartbeat();
return $acquired;
}

Expand All @@ -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)
Expand All @@ -95,11 +96,29 @@ public function release(): bool
#[Override]
public function forceRelease(): void
{
$this->stopHeartbeat();
$this->connection->table($this->table)
->where('key', $this->name)
->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;
}
Comment on lines +105 to +120
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

修复潜在的锁竞争条件。

当前实现存在以下问题:

  1. 主要问题 - 锁所有权冲突:第 109 行的 orWhere('expiration', '<=', time()) 条件允许心跳在锁过期后重新声明锁。这可能导致竞争条件:

    • 进程 A 持有锁
    • 锁过期
    • 进程 B 获取锁
    • 进程 A 的心跳运行并"窃取"锁
    • 结果:两个进程都认为自己拥有锁,违反互斥保证

    心跳的目的是延长当前持有的锁的过期时间,不应该在锁已过期后重新声明它。

  2. 必要重构 - 代码重复:第 107-113 行的更新逻辑与 acquire() 方法的第 60-66 行几乎完全相同,应该提取为共享辅助方法。

  3. 次要问题 - 不必要的 owner 更新:第 111 行更新了 owner 字段,但心跳只在进程已经拥有锁时运行,因此只需要更新 expiration

建议应用以下修复:

 #[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()))
+            ->where('owner', $this->owner)
             ->update([
-                'owner' => $this->owner,
                 'expiration' => $this->expiresAt(),
             ]);

         return $updated >= 1;
     }
     return true;
 }

此外,考虑提取共享的更新逻辑以减少 acquire()delayExpiration() 之间的代码重复。

🤖 Prompt for AI Agents
In src/lock/src/Driver/DatabaseLock.php around lines 103 to 118, the
delayExpiration() method currently allows updating the row when expiration <=
time(), which can let a heartbeat "steal" an expired lock and cause two
processes to believe they hold the lock; change the WHERE to only match the
current owner (remove the orWhere('expiration', '<=', time()) clause) so the
heartbeat only extends locks owned by this process, and update only the
expiration column (do not overwrite owner). Also refactor the update logic into
a shared private helper used by both acquire() and delayExpiration() to
eliminate duplication (move the query/update into one method that accepts the
matching where-clauses and the fields to update).


/**
* Get the UNIX timestamp indicating when the lock should expire.
*/
Expand Down
17 changes: 14 additions & 3 deletions src/lock/src/Driver/FileSystemLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
}

/**
Expand All @@ -54,6 +54,7 @@ public function acquire(): bool
public function release(): bool
{
if ($this->isOwnedByCurrentProcess()) {
$this->stopHeartbeat();
return $this->store->delete($this->name);
}

Expand All @@ -66,9 +67,19 @@ public function release(): bool
#[Override]
public function forceRelease(): void
{
$this->stopHeartbeat();
$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;
}
Comment on lines +74 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

验证父类在调用前是否检查锁所有权。

delayExpiration() 方法直接调用 $this->store->set() 来刷新锁的过期时间,但没有像 release() 方法(第56行)那样先验证当前进程是否拥有该锁。如果父类 AbstractLock 在调用此方法前没有进行所有权检查,可能会导致进程延长不属于自己的锁的过期时间。

运行以下脚本验证父类是否在调用 delayExpiration() 前检查所有权:


🏁 Script executed:

#!/bin/bash
# 描述:检查 AbstractLock 如何调用 delayExpiration() 以及是否进行所有权验证

# 查找 AbstractLock 中调用 delayExpiration 的位置及其上下文
ast-grep --pattern $'class AbstractLock {
  $$$
  delayExpiration()
  $$$
}'

# 搜索 AbstractLock 中调用 delayExpiration 的代码上下文
rg -A 10 -B 5 'delayExpiration\(\)' src/lock/src/Driver/AbstractLock.php

Length of output: 771


需要更完整的上下文信息。我来获取 AbstractLock 中包含 delayExpiration() 调用的完整方法:

Wait, I need to search the repository. Let me correct that:

---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full heartbeat method or enclosing function containing delayExpiration() call
rg -B 30 -A 5 'delayExpiration\(\)' src/lock/src/Driver/AbstractLock.php | head -80

Length of output: 1765


🏁 Script executed:

#!/bin/bash
# Get the full method containing delayExpiration() call with extensive context
rg -B 30 'delayExpiration\(\)' src/lock/src/Driver/AbstractLock.php

Length of output: 1639


需要验证 FileSystemLock 的 release() 方法是否确实执行了所有权检查,以及其他驱动程序的实现模式:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check FileSystemLock::release() for ownership verification
rg -B 5 -A 10 'public function release' src/lock/src/Driver/FileSystemLock.php

# Check if CacheLock has same delayExpiration() implementation
rg -B 3 -A 8 'protected function delayExpiration' src/lock/src/Driver/CacheLock.php

Length of output: 539


delayExpiration() 方法中添加所有权验证检查。

验证确认了原始审查的合理性。release() 方法通过 isOwnedByCurrentProcess() 检查所有权后才删除锁,而 delayExpiration() 方法直接调用 $this->store->set() 来延长锁的过期时间,没有进行任何所有权验证。这种不一致的模式使得代码可能允许进程延长不属于它的锁。

应在 delayExpiration() 方法中添加所有权检查,使其与 release() 方法的保护逻辑一致:

protected function delayExpiration(): bool
{
    if ($this->isOwnedByCurrentProcess() && $this->seconds > 0) {
        return $this->store->set($this->name, $this->owner, $this->seconds);
    }
    return false;
}
🤖 Prompt for AI Agents
In src/lock/src/Driver/FileSystemLock.php around lines 72-79, delayExpiration()
currently extends a lock's TTL without verifying ownership; add a check so the
method only calls $this->store->set(...) when $this->isOwnedByCurrentProcess()
is true and $this->seconds > 0, and return false otherwise (i.e., do not extend
locks not owned by the current process).


/**
* Returns the owner value written into the driver for this lock.
* @return string
Expand Down
17 changes: 14 additions & 3 deletions src/lock/src/Driver/RedisLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']) {
Expand All @@ -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;
Expand All @@ -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);
}

Expand All @@ -67,9 +68,19 @@ public function release(): bool
#[Override]
public function forceRelease(): void
{
$this->stopHeartbeat();
$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;
}
Comment on lines +75 to +82
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

delayExpiration() 缺少所有权验证。

delayExpiration() 方法直接调用 $this->store->set() 刷新过期时间,但没有验证当前进程是否拥有该锁。这与 release() 方法(第 62 行使用 Lua 脚本进行原子性的所有权检查和删除)形成对比。如果心跳协程在锁已被其他进程获取后继续运行,可能会导致进程延长不属于自己的锁的过期时间。

建议使用类似 release() 的 Lua 脚本方式来原子性地验证所有权并刷新过期时间:

protected function delayExpiration(): bool
{
    if ($this->seconds > 0) {
        $script = <<<'LUA'
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("expire", KEYS[1], ARGV[2])
else
    return 0
end
LUA;
        return (bool) $this->store->eval($script, [$this->name, $this->owner, $this->seconds], 1);
    }
    return true;
}


/**
* Returns the owner value written into the driver for this lock.
* @return string
Expand Down
18 changes: 18 additions & 0 deletions src/lock/src/Exception/HeartbeatException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);
/**
* This file is part of friendsofhyperf/components.
*
* @link https://github.com/friendsofhyperf/components
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
* @contact huangdijia@gmail.com
*/

namespace FriendsOfHyperf\Lock\Exception;

use Exception;

class HeartbeatException extends Exception
{
}
4 changes: 2 additions & 2 deletions src/lock/src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
/**
* @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);

if (is_null($name)) {
return $factory;
}

return $factory->make($name, $seconds, $owner, $driver);
return $factory->make($name, $seconds, $owner, $driver, $heartbeat);
}
3 changes: 2 additions & 1 deletion src/lock/src/Listener/RegisterPropertyHandlerListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
});
}
Expand Down
Loading