| 1 |
<?php |
| 2 |
|
| 3 |
namespace SyncBasalam\Services; |
| 4 |
|
| 5 |
defined('ABSPATH') || exit; |
| 6 |
|
| 7 |
/** Request metadata only: never persist cookies, nonces, payloads or API responses. */ |
| 8 |
class AsyncWorkerHealth |
| 9 |
{ |
| 10 |
public const OPTION_KEY = 'sync_basalam_async_worker_health'; |
| 11 |
|
| 12 |
private ?bool $protection = null; |
| 13 |
|
| 14 |
public function observeAsyncEnvironment(): void |
| 15 |
{ |
| 16 |
$this->protection = $this->detectLiteSpeedProtection(); |
| 17 |
$state = $this->getState(); |
| 18 |
$missing = $this->protection === false; |
| 19 |
if (($state['protection_missing'] ?? false) === $missing) return; |
| 20 |
|
| 21 |
$state['protection_missing'] = $missing; |
| 22 |
$state['environment_checked_at'] = time(); |
| 23 |
$this->save($state); |
| 24 |
} |
| 25 |
|
| 26 |
/** Call only while owning the runner's global lock, so a slow live worker is excluded. */ |
| 27 |
public function recordInterruptedJob(object $job): void |
| 28 |
{ |
| 29 |
$state = $this->getState(); |
| 30 |
$interruption = [ |
| 31 |
'job_id' => (int) $job->id, |
| 32 |
'started_at' => (int) $job->started_at, |
| 33 |
]; |
| 34 |
if (($state['interruption'] ?? null) === $interruption) return; |
| 35 |
|
| 36 |
$state['interruption'] = $interruption; |
| 37 |
$state['interrupted_at'] = time(); |
| 38 |
$state['litespeed'] = $this->isLiteSpeed(); |
| 39 |
$this->save($state); |
| 40 |
} |
| 41 |
|
| 42 |
/** A returned API rejection is distinct from an interrupted PHP worker. */ |
| 43 |
public function recordJobReturned(object $job, float $elapsed): void |
| 44 |
{ |
| 45 |
$state = $this->getState(); |
| 46 |
$state['last_returned_job_id'] = (int) $job->id; |
| 47 |
$state['last_returned_at'] = time(); |
| 48 |
$state['last_duration_seconds'] = round($elapsed, 3); |
| 49 |
$state['last_returned_protected'] = $this->protection; |
| 50 |
// On LiteSpeed, a quick job without noabort does not prove the host is repaired. |
| 51 |
if ($this->protection !== false) { |
| 52 |
unset($state['interruption'], $state['interrupted_at'], $state['litespeed']); |
| 53 |
} |
| 54 |
$this->save($state); |
| 55 |
} |
| 56 |
|
| 57 |
public function getNotice(): ?array |
| 58 |
{ |
| 59 |
$state = $this->getState(); |
| 60 |
$interrupted = !empty($state['interruption']); |
| 61 |
$missing = !empty($state['protection_missing']); |
| 62 |
if (!$interrupted && !$missing) return null; |
| 63 |
|
| 64 |
$litespeed = $missing || !empty($state['litespeed']); |
| 65 |
return [ |
| 66 |
'class' => $interrupted ? 'notice-error' : 'notice-warning', |
| 67 |
'title' => $interrupted ? 'اجرای ه� |
| 68 |
گا� |
| 69 |
سازی نی� |
| 70 |
هکاره � |
| 71 |
توقف شده است.' : '� |
| 72 |
حافظ اجرای پسز� |
| 73 |
ینه در هاست قابل تأیید نیست.', |
| 74 |
'message' => $interrupted |
| 75 |
? 'یک کار ه� |
| 76 |
گا� |
| 77 |
سازی بیش از دو دقیقه در حالت اجرا باقی � |
| 78 |
انده، ا� |
| 79 |
ا پردازشگر آن دیگر فعال نیست. � |
| 80 |
� |
| 81 |
کن است قی� |
| 82 |
ت، � |
| 83 |
وجودی یا سفارشها بهروز نشده باشند.' |
| 84 |
: 'در درخواست اجرای صف روی هاست LiteSpeed، � |
| 85 |
حافظ جلوگیری از توقف شناسایی نشد. � |
| 86 |
� |
| 87 |
کن است کارهای ه� |
| 88 |
گا� |
| 89 |
سازی نی� |
| 90 |
هکاره � |
| 91 |
توقف شوند.', |
| 92 |
'guidance' => $litespeed |
| 93 |
? 'از پشتیبانی هاست بخواهید تنظی� |
| 94 |
noabort را فقط برای درخواست اجرای صف ووسلا� |
| 95 |
در .htaccess یا تنظی� |
| 96 |
ات وبسرور بررسی کند. این تنظی� |
| 97 |
� |
| 98 |
ربوط به هاست است و به فعال بودن افزونهٔ LiteSpeed Cache وابسته نیست.' |
| 99 |
: 'از پشتیبانی هاست بخواهید علت توقف پردازش PHP و � |
| 100 |
حدودیت ز� |
| 101 |
ان یا حافظه را بررسی کند.', |
| 102 |
'job_id' => (int) ($state['interruption']['job_id'] ?? 0), |
| 103 |
]; |
| 104 |
} |
| 105 |
|
| 106 |
private function getState(): array |
| 107 |
{ |
| 108 |
$state = get_option(self::OPTION_KEY, []); |
| 109 |
return is_array($state) ? $state : []; |
| 110 |
} |
| 111 |
|
| 112 |
private function save(array $state): void |
| 113 |
{ |
| 114 |
update_option(self::OPTION_KEY, $state, false); |
| 115 |
} |
| 116 |
|
| 117 |
protected function isLiteSpeed(): bool |
| 118 |
{ |
| 119 |
return PHP_SAPI === 'litespeed'; |
| 120 |
} |
| 121 |
|
| 122 |
/** null means a different PHP runtime; absence cannot rule out a global host setting. */ |
| 123 |
protected function detectLiteSpeedProtection(): ?bool |
| 124 |
{ |
| 125 |
if (!$this->isLiteSpeed()) return null; |
| 126 |
|
| 127 |
foreach (['noabort', 'REDIRECT_noabort'] as $name) { |
| 128 |
$values = [$_SERVER[$name] ?? null, getenv($name)]; |
| 129 |
if (function_exists('apache_getenv')) $values[] = apache_getenv($name); |
| 130 |
foreach ($values as $value) { |
| 131 |
if ((string) $value === '1') return true; |
| 132 |
} |
| 133 |
} |
| 134 |
return false; |
| 135 |
} |
| 136 |
} |
| 137 |
|