Files
XC_VM/tests/Unit/PhpFpmWorkerPidsTest.php
T
Claude 6bc29e8ee7 fix(users): date a remote worker pid by its last write and tell idle pools from unknown
Review fixes for the php_pids change.

- cron:users judged a remote PHP-served connection by date_start, but a
  reused connection (VOD range/seek, TS or timeshift reconnect) gets a new
  worker pid and keeps its original date_start. A pid assigned after the
  node's last heartbeat was then looked up in a list that could not hold
  it, and the live viewer was killed. The pid write also stamps
  hls_last_read (MAIN's clock), so the later of the two now dates the pid.
  The check moves into isRemoteWorkerRunning().
- phpFpmWorkerPIDs() returns null when no FPM master is visible, so an
  idle ondemand pool ("[]") now reaps dead workers' rows while an unknown
  list still keeps them. A server missing from either map no longer hits
  count(null).
- getAllSimple/getStreamingSimple/getProxySimple and the admin API
  get_server leave out php_pids; getById keeps the full row for REPLACE.
- Tests pin the watchdog and reaper wiring, exercise sort(), and reset
  the ServerRepository db seam; array_unique() is dropped (unreachable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQHeypQpfAXTdCysmi7b6a
2026-09-25 00:04:16 +00:00

84 lines
3.4 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
use XcVm\Core\Process\ProcessManager;
/**
* ProcessManager::phpFpmWorkerPIDs — the pid list the watchdog publishes as
* servers.php_pids. A connection row's pid is the PHP-FPM worker serving it
* (getmypid() in live/vod/timeshift), so the list must hold the workers of
* the xc_vm pool. It used to hold the FPM master pids from the pool pid files,
* which never match a worker: in Redis mode MAIN's cron:users then closed
* every PHP-served LB viewer about a minute after it connected. An idle pool
* (pm = ondemand) has no workers, so [] means "none running" only while an FPM
* master is visible; with none, the list is unknown (null).
*
* Driven against a fixture /proc tree. Its pids are above the kernel's
* PID_MAX_LIMIT (4194304), so none can be this process's own pid, which
* findProcessPIDs() skips.
*/
final class PhpFpmWorkerPidsTest extends TestCase {
private string $rRoot;
protected function setUp(): void {
$this->rRoot = sys_get_temp_dir() . '/xcvm_proc_' . uniqid('', true);
mkdir($this->rRoot, 0755, true);
}
protected function tearDown(): void {
foreach (glob($this->rRoot . '/*/cmdline') ?: [] as $rFile) {
unlink($rFile);
rmdir(dirname($rFile));
}
@rmdir($this->rRoot);
}
private function process(int $rPID, string $rCmdline): void {
mkdir($this->rRoot . '/' . $rPID);
file_put_contents($this->rRoot . '/' . $rPID . '/cmdline', $rCmdline);
}
public function testListsTheXcVmPoolWorkersOnly(): void {
$this->process(4300001, "php-fpm: master process (/home/xc_vm/bin/php/etc/1.conf)\0");
// FPM overwrites the worker's argv with its title and pads the rest with NULs.
$this->process(4300002, 'php-fpm: pool xc_vm' . str_repeat("\0", 40));
$this->process(4300003, 'php-fpm: pool xc_vm');
$this->process(4300004, "/home/xc_vm/bin/php/bin/php\0console.php\0watchdog\0");
$this->process(4300005, 'php-fpm: pool www');
// Globbed before 4300002 ("43000010" < "4300002"); listed last (numeric order).
$this->process(43000010, 'php-fpm: pool xc_vm');
$this->assertSame([4300002, 4300003, 43000010], ProcessManager::phpFpmWorkerPIDs($this->rRoot));
}
public function testAnIdlePoolYieldsAnEmptyList(): void {
$this->process(4300001, "php-fpm: master process (/home/xc_vm/bin/php/etc/1.conf)\0");
$this->assertSame([], ProcessManager::phpFpmWorkerPIDs($this->rRoot));
}
public function testNoVisibleFpmMasterMeansUnknown(): void {
$this->assertNull(ProcessManager::phpFpmWorkerPIDs($this->rRoot));
$this->process(4300004, "/home/xc_vm/bin/php/bin/php\0console.php\0watchdog\0");
$this->assertNull(ProcessManager::phpFpmWorkerPIDs($this->rRoot));
}
public function testFindProcessPIDsReadsTheGivenProcRoot(): void {
$this->assertSame([], ProcessManager::findProcessPIDs(['x'], 0, $this->rRoot));
$this->process(4300007, "ffmpeg\0-i\0x\0");
$this->assertSame([4300007], ProcessManager::findProcessPIDs(['ffmpeg'], 0, $this->rRoot));
}
public function testWatchdogPublishesTheWorkerList(): void {
$rPath = MAIN_HOME . 'Cli/Commands/WatchdogCommand.php';
$this->assertFileExists($rPath);
$rSource = (string) file_get_contents($rPath);
$this->assertSame(1, substr_count($rSource, 'ProcessManager::phpFpmWorkerPIDs()'), 'the watchdog must publish the FPM worker pids as php_pids');
$this->assertStringNotContainsString('bin/php/sockets/*.pid', $rSource, 'the pool pid files hold only the FPM masters');
}
}