| | |
| | | <?php |
| | | namespace JVBase\managers\queue; |
| | | use wpdb; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | | } |
| | |
| | | class Locker |
| | | { |
| | | private string $lockKey; |
| | | private int $ttl; |
| | | private int $timeout; |
| | | protected wpdb $wpdb; |
| | | private ?string $token = null; |
| | | |
| | | public function __construct(string $key = 'queue_processor', int $ttl = 300) |
| | | public function __construct(string $key = 'queue', int $timeout = 0) |
| | | { |
| | | $this->lockKey = BASE . '_lock_' . $key; |
| | | $this->ttl = $ttl; |
| | | } |
| | | |
| | | public function acquire(): bool |
| | | { |
| | | // Generate unique token for this process |
| | | $this->token = uniqid(gethostname() . '_', true); |
| | | |
| | | // SET NX with TTL - atomic "set if not exists" |
| | | $result = wp_cache_add($this->lockKey, $this->token, 'locks', $this->ttl); |
| | | |
| | | return $result === true; |
| | | } |
| | | |
| | | public function release(): void |
| | | { |
| | | if (!$this->token) { |
| | | return; |
| | | } |
| | | |
| | | // Only release if we own the lock |
| | | $current = wp_cache_get($this->lockKey, 'locks'); |
| | | if ($current === $this->token) { |
| | | wp_cache_delete($this->lockKey, 'locks'); |
| | | } |
| | | |
| | | $this->token = null; |
| | | } |
| | | |
| | | public function isLocked(): bool |
| | | { |
| | | return wp_cache_get($this->lockKey, 'locks') !== false; |
| | | $this->lockKey = BASE . $key . '_lock'; |
| | | $this->timeout = $timeout; |
| | | global $wpdb; |
| | | $this->wpdb = $wpdb; |
| | | } |
| | | |
| | | /** |
| | | * Execute callback with lock, auto-release after |
| | | */ |
| | | public function withLock(callable $callback): mixed |
| | | public function withLock(callable $callback): void |
| | | { |
| | | if (!$this->acquire()) { |
| | | return null; |
| | | $acquired = $this->wpdb->get_var( |
| | | $this->wpdb->prepare( |
| | | 'SELECT GET_LOCK(%s, %d)', |
| | | $this->lockKey, |
| | | $this->timeout |
| | | ) |
| | | ); |
| | | |
| | | if ((int) $acquired !== 1) { |
| | | // Lock already held — just exit quietly |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | return $callback(); |
| | | $callback(); |
| | | } finally { |
| | | $this->release(); |
| | | $this->wpdb->get_var( |
| | | $this->wpdb->prepare( |
| | | 'SELECT RELEASE_LOCK(%s)', |
| | | $this->lockKey |
| | | ) |
| | | ); |
| | | } |
| | | } |
| | | } |