Files
XC_VM/tests/Unit/LazyDatabaseHandlerTest.php
T
obscuremind 366734dc1f feat(cluster): mode 2 with the Redis connection handler
A load balancer in mode 2 never opens MAIN's Redis: the stream endpoints
open their store through ConnectionTracker::openStore(), which takes the
database's lazy handle there, and ShutdownHandler leaves a close the agent
did not take to no store in mode 2. Nothing past that needed MAIN's Redis
on such a node: every store path asks the agent first, and the daemons
and crons that run there skip Redis already. MAIN takes either store.

So the refusals go: the move to mode 2, enrolment, server:install and
LbInstallFlow, the panel's install and reinstall, New Node Mode api, and
the Cache page's switch, with their strings and the REDIS_HANDLER reason.
2026-10-07 13:38:39 +00:00

97 lines
3.6 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
use XcVm\Core\Database\DatabaseUnavailableException;
use XcVm\Core\Database\LazyDatabaseHandler;
use XcVm\Infrastructure\Database\DatabaseFactory;
/**
* Streaming endpoints get a LazyDatabaseHandler: no MySQL connection to MAIN
* until the first query, so a request answered from cache or Redis opens none.
* The real connect goes through the xcvm_core extension; here it is replaced
* by a connection to the test server that counts how often it is opened.
*/
final class LazyDatabaseHandlerTest extends TestCase {
private function handler(): LazyDatabaseHandler {
return new class extends LazyDatabaseHandler {
public int $rOpens = 0;
public function db_connect(bool $migrate = false, ?bool $graceful = null) {
$this->rOpens++;
$this->dbh = \TestDb::connect();
$this->connected = true;
return true;
}
};
}
protected function tearDown(): void {
DatabaseFactory::reset();
unset($GLOBALS['db']);
}
public function testNothingOpensUntilTheFirstQuery(): void {
$rDb = $this->handler();
$this->assertFalse($rDb->isOpen());
$this->assertTrue($rDb->ping(), 'an unopened handle is usable, not dead');
$this->assertSame(0, $rDb->rOpens);
$this->assertTrue($rDb->query('SELECT 1 AS `one`'));
$this->assertSame(1, $rDb->rOpens);
$this->assertInstanceOf(\PDO::class, $rDb->dbh);
$rDb->query('SELECT ? AS `two`', 2);
$this->assertSame(1, $rDb->rOpens, 'opened once');
$this->assertTrue($rDb->isOpen());
}
public function testEscapeAndTransactionsOpenToo(): void {
$rDb = $this->handler();
$this->assertSame("'x'", $rDb->escape('x'));
$this->assertSame(1, $rDb->rOpens);
$rTx = $this->handler();
$this->assertTrue($rTx->beginTransaction());
$this->assertSame(1, $rTx->rOpens);
$rTx->rollback();
}
/** Graceful (MAIN's cluster entry point): MySQL down throws, once tried, and every later use throws without trying again. */
public function testAGracefulHandleThrowsWhenTheDatabaseIsDown(): void {
$rDb = new class (true) extends LazyDatabaseHandler {
public int $rOpens = 0;
public function db_connect(bool $migrate = false, ?bool $graceful = null) {
$this->rOpens++;
return $graceful === true ? false : throw new \LogicException('an exit() on a graceful handle');
}
};
foreach (['query', 'escape', 'beginTransaction'] as $rUse) {
try {
$rUse === 'query' ? $rDb->query('SELECT 1') : $rDb->{$rUse}('x');
$this->fail($rUse . ' went on without a database');
} catch (DatabaseUnavailableException) {
}
}
$this->assertSame(1, $rDb->rOpens, 'tried once: no reconnect loop per query');
}
public function testConnectLazyKeepsAnUnopenedHandle(): void {
$GLOBALS['db'] = $this->handler();
DatabaseFactory::connectLazy();
$this->assertSame($GLOBALS['db'], DatabaseFactory::get());
$this->assertSame(0, $GLOBALS['db']->rOpens, 'reused, not replaced, not opened');
}
public function testStreamingEndpointsUseTheLazyHandler(): void {
$rRoot = MAIN_HOME;
foreach (['Public/stream/live.php', 'Public/stream/vod.php', 'Public/stream/timeshift.php', 'Streaming/Lifecycle/ShutdownHandler.php'] as $rFile) {
$rSource = (string) file_get_contents($rRoot . $rFile);
$this->assertMatchesRegularExpression('/DatabaseFactory::connectLazy\(\);|ConnectionTracker::openStore\(\$rSettings\);/', $rSource, $rFile);
$this->assertStringNotContainsString('DatabaseFactory::connect();', $rSource, $rFile);
}
// Where the endpoints open their store.
$this->assertMatchesRegularExpression('/function openStore\(.*?DatabaseFactory::connectLazy\(\);/s', (string) file_get_contents($rRoot . 'Domain/Stream/ConnectionTracker.php'));
}
}