PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / helpers / lock-helper.php

lock-helper.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at src/helpers/lock-helper.php

86 lines 2.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\Helpers;
4
5 use Yoast\WP\SEO\Exceptions\Locking\Lock_Timeout_Exception;
6 use Yoast\WP\SEO\Expiring_Store\Application\Expiring_Store;
7
8 /**
9 * Distributed exclusive lock backed by the expiring store.
10 *
11 * Acquires a network-scoped lock in the expiring store table, executes the
12 * callback, and releases the lock in a finally block.
13 *
14 * The lock has a TTL so that it self-expires if the owning process crashes without
15 * releasing it. Other processes retry with a configurable delay between attempts.
16 */
17 class Lock_Helper {
18
19 /**
20 * The expiring store.
21 *
22 * @var Expiring_Store
23 */
24 private $store;
25
26 /**
27 * The constructor.
28 *
29 * @param Expiring_Store $store The expiring store.
30 */
31 public function __construct( Expiring_Store $store ) {
32 $this->store = $store;
33 }
34
35 /**
36 * Acquires a lock, executes the callback, and releases the lock.
37 *
38 * @template T
39 *
40 * @param string $lock_key The lock key.
41 * @param callable(): T $callback The callback to execute while holding the lock.
42 * @param int $ttl_in_seconds The lock TTL in seconds. Defaults to 30.
43 * @param int $max_attempts The maximum number of acquisition attempts. Defaults to 5.
44 * @param int $retry_delay_microseconds The delay between attempts in microseconds. Defaults to 20000 (20ms).
45 *
46 * @return T The callback's return value.
47 * @throws Lock_Timeout_Exception When the lock cannot be acquired within the allowed attempts.
48 */
49 public function execute( string $lock_key, callable $callback, int $ttl_in_seconds = 30, int $max_attempts = 5, int $retry_delay_microseconds = 20_000 ) {
50 $this->acquire( $lock_key, $ttl_in_seconds, $max_attempts, $retry_delay_microseconds );
51
52 try {
53 return $callback();
54 }
55 finally {
56 $this->store->delete_for_multisite( $lock_key );
57 }
58 }
59
60 /**
61 * Attempts to acquire the lock with retries.
62 *
63 * @param string $lock_key The lock key.
64 * @param int $ttl_in_seconds The lock TTL in seconds.
65 * @param int $max_attempts The maximum number of attempts.
66 * @param int $retry_delay_microseconds The delay between attempts in microseconds.
67 *
68 * @return void
69 * @throws Lock_Timeout_Exception When all attempts are exhausted.
70 */
71 private function acquire( string $lock_key, int $ttl_in_seconds, int $max_attempts, int $retry_delay_microseconds ): void {
72 for ( $attempt = 1; $attempt <= $max_attempts; $attempt++ ) {
73 if ( $this->store->persist_if_absent_for_multisite( $lock_key, true, $ttl_in_seconds ) ) {
74 return;
75 }
76
77 if ( $attempt < $max_attempts ) {
78 \usleep( $retry_delay_microseconds );
79 }
80 }
81
82 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- This is an exception message, not output.
83 throw new Lock_Timeout_Exception( $lock_key, $max_attempts );
84 }
85 }
86