PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / repositories / PermalinkCacheScheduleLock.php

PermalinkCacheScheduleLock.php in 404 Solution trunk, at includes/repositories/PermalinkCacheScheduleLock.php

69 lines 2.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // allow-no-test-found: exercised by PermalinkCacheChainAlreadyArmedTest race and stale-owner scenarios.
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 require_once dirname(__DIR__) . '/core/ExclusiveOptionRow.php';
10
11 /**
12 * Serializes the permalink-cache hook probe with its WordPress cron write.
13 *
14 * WordPress de-duplicates scheduled events by hook and arguments. The cache
15 * chain changes its arguments on every link, so two callers that both inspect
16 * an empty cron store can otherwise both add a link. This claim makes that
17 * check-and-write critical section exclusive across PHP requests.
18 */
19 final class ABJ_404_Solution_PermalinkCacheScheduleLock {
20
21 private const OPTION_NAME = 'abj404_permalink_cache_schedule_lock';
22
23 /** A killed request may delay, but never permanently wedge, the chain. */
24 private const TTL_SECONDS = 180;
25
26 /**
27 * @return string|null Exact owner value, or null while another live caller owns it.
28 * @phpstan-impure
29 */
30 public function acquire(): ?string {
31 $lockRow = $this->lockRow();
32 $now = abj_clock()->now();
33 $claimValue = ABJ_404_Solution_ExclusiveOptionRow::uniqueClaimValue(
34 (string)($now + self::TTL_SECONDS)
35 );
36 $claim = array('optionName' => self::OPTION_NAME, 'value' => $claimValue);
37 if ($lockRow->claim($claim)) {
38 return $claimValue;
39 }
40
41 $existing = $lockRow->valueOf(self::OPTION_NAME);
42 $expiryPart = explode(':', $existing, 2)[0];
43 $existingIsStale = $existing === '' || !is_numeric($expiryPart) || (int)$expiryPart <= $now;
44 if (!$existingIsStale) {
45 return null;
46 }
47 if ($existing !== '' && !$lockRow->releaseIfValueIs(array(
48 'optionName' => self::OPTION_NAME,
49 'value' => $existing,
50 ))) {
51 return null;
52 }
53
54 return $lockRow->claim($claim) ? $claimValue : null;
55 }
56
57 /** Release only the exact claim this instance acquired. */
58 public function release(string $claimValue): void {
59 $this->lockRow()->releaseIfValueIs(array(
60 'optionName' => self::OPTION_NAME,
61 'value' => $claimValue,
62 ));
63 }
64
65 private function lockRow(): ABJ_404_Solution_ExclusiveOptionRow {
66 return new ABJ_404_Solution_ExclusiveOptionRow();
67 }
68 }
69