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 / gsc / GscFetchLock.php

GscFetchLock.php in 404 Solution trunk, at includes/gsc/GscFetchLock.php

137 lines 4.6 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 GscFetchPipelineTest contention and migration scenarios
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 require_once __DIR__ . '/GscConfig.php';
10 require_once __DIR__ . '/GscFetchLease.php';
11 require_once __DIR__ . '/GscFetchLockStore.php';
12
13 /** Coordinates one renewable GSC fetch lease across requests and upgrades. */
14 final class ABJ_404_Solution_GscFetchLock {
15
16 /** @var ABJ_404_Solution_Logging */
17 private $logger;
18
19 /** @var ABJ_404_Solution_GscFetchLease|null */
20 private $lease;
21
22 /** @var ABJ_404_Solution_GscFetchLockStore */
23 private $store;
24
25 /** @var int|null */
26 private $atomicReadyAt;
27
28 /** @param ABJ_404_Solution_Logging $logger */
29 public function __construct($logger) {
30 $this->logger = $logger;
31 $this->store = new ABJ_404_Solution_GscFetchLockStore();
32 }
33
34 /** Initialize and, when absent, persist the atomic-lock migration deadline. */
35 public function initializeAtomicLockMigrationState(): void {
36 if ($this->atomicReadyAt === null) {
37 $this->atomicReadyAt = $this->initializeAtomicReadyAt();
38 }
39 }
40
41 public function claim(): bool {
42 $this->initializeAtomicLockMigrationState();
43 $now = abj_clock()->now();
44 $readyAt = $this->atomicReadyAt === null ? PHP_INT_MAX : $this->atomicReadyAt;
45 if ($now < $readyAt
46 || $this->store->legacyOwner() !== false
47 ) {
48 return false;
49 }
50 $value = ABJ_404_Solution_ExclusiveOptionRow::uniqueClaimValue((string)$now);
51 if ($this->store->claim(array('value' => $value))) {
52 $this->lease = $this->atomicLease($value, $now);
53 return true;
54 }
55 $heldSince = $this->store->owner();
56 if (!$this->hasAgedOut($heldSince, $now)) {
57 return false;
58 }
59 $this->store->release(array('value' => $heldSince));
60 $value = ABJ_404_Solution_ExclusiveOptionRow::uniqueClaimValue((string)$now);
61 if (!$this->store->claim(array('value' => $value))) {
62 return false;
63 }
64 $this->lease = $this->atomicLease($value, $now);
65 return true;
66 }
67
68 public function release(): void {
69 if ($this->lease === null) {
70 return;
71 }
72 $this->store->release(array('value' => $this->lease->value()));
73 $this->lease = null;
74 }
75
76 public function isHeld(): bool {
77 if ($this->atomicReadyAt === null) {
78 return true;
79 }
80 $now = abj_clock()->now();
81 if ($now < $this->atomicReadyAt) {
82 return true;
83 }
84 if ($this->store->legacyOwner() !== false) {
85 return true;
86 }
87 $heldSince = $this->store->owner();
88 return $heldSince !== '' && !$this->hasAgedOut($heldSince, $now);
89 }
90
91 public function renewIfDue(): bool {
92 if ($this->lease === null) {
93 return true;
94 }
95 $now = abj_clock()->now();
96 if (($now - $this->lease->renewedAt()) < intdiv(ABJ_404_Solution_GscConfig::LOCK_TTL, 3)) {
97 return true;
98 }
99 $replacement = ABJ_404_Solution_ExclusiveOptionRow::uniqueClaimValue((string)$now);
100 if (!$this->store->renew(array(
101 'currentValue' => $this->lease->value(),
102 'replacementValue' => $replacement,
103 ))) {
104 $this->logger->warn('Lost the GSC fetch lock while renewing it; stopping before another API request.');
105 return false;
106 }
107 $this->lease = $this->lease->renewed($replacement, $now);
108 return true;
109 }
110
111 private function initializeAtomicReadyAt(): int {
112 $rawReadyAt = $this->store->atomicReadyAt();
113 if ($rawReadyAt === false || !is_numeric($rawReadyAt)) {
114 $readyAt = abj_clock()->now() + ABJ_404_Solution_GscConfig::ATOMIC_LOCK_MIGRATION_DELAY;
115 if (!$this->store->persistAtomicReadyAt($readyAt)) {
116 $this->logger->warn('Could not persist the GSC atomic-lock migration deadline; GSC fetches remain paused.');
117 return PHP_INT_MAX;
118 }
119 return $readyAt;
120 }
121 return max(0, (int)$rawReadyAt);
122 }
123
124 private function hasAgedOut(string $value, int $now): bool {
125 $timestamp = explode(':', $value, 2)[0];
126 return $value === '' || !is_numeric($timestamp)
127 || ($now - (int)$timestamp) > ABJ_404_Solution_GscConfig::LOCK_TTL;
128 }
129
130 private function atomicLease(string $value, int $now): ABJ_404_Solution_GscFetchLease {
131 return ABJ_404_Solution_GscFetchLease::fromState(array(
132 'value' => $value,
133 'renewedAt' => $now,
134 ));
135 }
136 }
137