mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-03 12:02:29 +02:00
A new monitor takes a few hundred milliseconds to write its _.monitor file. Every viewer that reached a stopped on-demand stream inside that window found it unwatched and started it again: the later viewer deleted the _.monitor and _.pid the earlier monitor had just written, each monitor's checkRunning ran before the other had set its process title, and both launched a producer — two connections to a source that often allows one, kicking each other off. The check-and-start in live.php now runs under a per-stream flock (<id>_.start on the streams tmpfs). The viewers behind the first wait on it, re-read the monitor pid, and find the stream started. A viewer that dies releases it with its process. The test runs four simultaneous viewers in separate processes: one start with the lock, four without. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EuZvjFSdodqgpyXtaoH1Xt
56 lines
1.8 KiB
PHP
56 lines
1.8 KiB
PHP
<?php
|
|
|
|
namespace XcVm\Tests\Unit;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use XcVm\Domain\Stream\StreamProcess;
|
|
|
|
/**
|
|
* Viewers who reach a stopped on-demand stream at the same moment must start
|
|
* it once. Each simulated viewer below runs live.php's shape in its own
|
|
* process: take the start lock, look for a monitor, and — finding none — spend
|
|
* the monitor's start-up window starting one.
|
|
*/
|
|
class OnDemandStartLockTest extends TestCase {
|
|
private string $rDir;
|
|
|
|
protected function setUp(): void {
|
|
if (!defined('STREAMS_PATH')) {
|
|
define('STREAMS_PATH', sys_get_temp_dir() . '/xcvm-ondemand-lock/');
|
|
}
|
|
$this->rDir = STREAMS_PATH;
|
|
@mkdir($this->rDir, 0777, true);
|
|
@unlink($this->rDir . '77_.started');
|
|
@unlink($this->rDir . '77_.start');
|
|
}
|
|
|
|
private function viewer(): mixed {
|
|
$rCode = 'require ' . var_export(dirname(__DIR__) . '/bootstrap.php', true) . ';'
|
|
. 'define("STREAMS_PATH", ' . var_export($this->rDir, true) . ');'
|
|
. '$l = \XcVm\Domain\Stream\StreamProcess::lockOnDemandStart(77);'
|
|
. '$m = STREAMS_PATH . "77_.started";'
|
|
. 'if (!file_exists($m)) { usleep(300000); file_put_contents($m, getmypid() . "\n", FILE_APPEND); }'
|
|
. '\XcVm\Domain\Stream\StreamProcess::unlockOnDemandStart($l);';
|
|
|
|
return proc_open([PHP_BINARY, '-r', $rCode], [], $rPipes);
|
|
}
|
|
|
|
public function testViewersArrivingTogetherStartTheStreamOnce(): void {
|
|
$rViewers = [];
|
|
for ($i = 0; $i < 4; $i++) {
|
|
$rViewers[] = $this->viewer();
|
|
}
|
|
foreach ($rViewers as $rProc) {
|
|
proc_close($rProc);
|
|
}
|
|
|
|
$rStarts = array_filter(explode("\n", (string) @file_get_contents($this->rDir . '77_.started')));
|
|
$this->assertCount(1, $rStarts, 'monitors started: ' . count($rStarts));
|
|
}
|
|
|
|
public function testUnlockToleratesNoLock(): void {
|
|
StreamProcess::unlockOnDemandStart(null);
|
|
$this->assertTrue(true);
|
|
}
|
|
}
|