mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-31 19:03:43 +01:00
From a review of how the panel uses Redis for its viewers. Viewers cut or kept wrongly: - A streaming worker's five-minute check-in ended the stream when the store could not be asked: "Redis did not answer" read as "the connection is gone", so a Redis restart cut every TS, VOD and timeshift viewer whose check-in fell in it. ConnectionTracker::checkIn() goes on, asks again in 30 s, and stops after ten minutes of silence. - The sweep closed a connection that went silent with a kill -9 of the pid its record named. That pid is a PHP-FPM worker which could not write its close and serves another viewer since. The sweep's close kills nothing. - The on-demand watcher read "Redis did not answer" as "no viewers" and could stop a watched stream. It skips the pass. - The enable-time copy of lines_live into Redis deleted the rows without looking at the transaction's answer, and loaded them over what Redis kept from when the handler was last on: that snapshot is emptied first when the handler really was off. Work done for nothing: - With the viewers in MySQL (the default) the limiter still connected to Redis on every viewer request. Redis is asked for only where they are. - fanout_sync read one record per viewer every ten seconds; cron:streams read all of a server's viewers again for each direct-proxy stream; the on-demand watcher read and decoded every record of every running stream each pass; the proxies page decoded every record of the parent servers for one number. Each reads once, in batches, or the set's size. - Batched reads went as transactions (a round trip a command) and counts fetched whole member lists: a pipeline, and ZCARD. - Each playback start counted every server's viewers and wrote a file that nothing read. The balancer takes the load as it was last counted while that is three seconds old (ConnectionTracker::capacity()). - LINE_ALL# and CONNECTIONS were written for every connection and read by nothing: no longer written (still cleared, for what an older node wrote). - A stored user agent is 255 bytes, as the table holds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
3.3 KiB
PHP
69 lines
3.3 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use XcVm\Core\Cache\FileCache;
|
|
use XcVm\Domain\Stream\ConnectionTracker;
|
|
use XcVm\Infrastructure\Database\DatabaseFactory;
|
|
use XcVm\Infrastructure\Redis\RedisManager;
|
|
|
|
/**
|
|
* The servers' load, where nothing set the global servers: the cluster API,
|
|
* whose rtmp_auth places a load balancer's RTMP viewer (StreamRedirector)
|
|
* and threw on array_keys(null) with the Redis connection handler on.
|
|
*/
|
|
final class ServerCapacityTest extends TestCase {
|
|
public function testItReadsTheServersWhereNoGlobalOnesAreSet(): void {
|
|
$rDb = new TestDb();
|
|
$rDb->exec('CREATE TABLE `lines_live` (`server_id` int, `proxy_id` int DEFAULT 0, `hls_end` int DEFAULT 0)');
|
|
$rDb->exec('INSERT INTO `lines_live` (`server_id`) VALUES (2), (2)');
|
|
DatabaseFactory::set($rDb);
|
|
RedisManager::closeInstance();
|
|
RedisManager::useConnector(static fn() => null);
|
|
FileCache::setCache('servers', [2 => ['server_online' => 1, 'server_hardware' => '{}', 'network_guaranteed_speed' => 1000, 'watchdog' => ['bytes_sent' => 0], 'total_clients' => 10]]);
|
|
$rWas = [$GLOBALS['rSettings'] ?? null, $GLOBALS['rServers'] ?? null];
|
|
$GLOBALS['rSettings'] = ['redis_handler' => 0, 'split_by' => 'band'];
|
|
unset($GLOBALS['rServers']);
|
|
try {
|
|
$rRows = ConnectionTracker::getCapacity();
|
|
} finally {
|
|
[$GLOBALS['rSettings'], $GLOBALS['rServers']] = $rWas;
|
|
DatabaseFactory::reset();
|
|
RedisManager::useConnector(null);
|
|
FileCache::deleteCache('servers');
|
|
}
|
|
$this->assertSame(2, (int) $rRows[2]['online_clients']);
|
|
$this->assertSame(0.0, $rRows[2]['capacity'], 'nothing sent yet, on its guaranteed speed');
|
|
}
|
|
|
|
/**
|
|
* A viewer is placed by the load as it was last counted, while that is a
|
|
* few seconds old (the watchdog counts and leaves the file): each playback
|
|
* start counted every server's viewers itself, and the file was read by nothing.
|
|
*/
|
|
public function testAViewerIsPlacedByTheLoadLastCounted(): void {
|
|
defined('CACHE_TMP_PATH') || define('CACHE_TMP_PATH', sys_get_temp_dir() . '/xcvm-capacity-' . getmypid() . '/');
|
|
is_dir(CACHE_TMP_PATH) || mkdir(CACHE_TMP_PATH, 0775, true);
|
|
$rFile = CACHE_TMP_PATH . 'servers_capacity';
|
|
$rDb = new TestDb();
|
|
$rDb->exec('CREATE TABLE `lines_live` (`server_id` int, `proxy_id` int DEFAULT 0, `hls_end` int DEFAULT 0)');
|
|
$rDb->exec('INSERT INTO `lines_live` (`server_id`) VALUES (2), (2), (2)');
|
|
DatabaseFactory::set($rDb);
|
|
RedisManager::closeInstance();
|
|
$rWas = [$GLOBALS['rSettings'] ?? null, $GLOBALS['rServers'] ?? null];
|
|
$GLOBALS['rSettings'] = ['redis_handler' => 0, 'split_by' => 'conn'];
|
|
$GLOBALS['rServers'] = [2 => ['server_online' => 1]];
|
|
try {
|
|
file_put_contents($rFile, json_encode([2 => ['server_id' => '2', 'online_clients' => '1', 'capacity' => '1']]));
|
|
$this->assertSame('1', ConnectionTracker::capacity()[2]['online_clients'], 'counted a moment ago: taken as it is');
|
|
|
|
touch($rFile, time() - ConnectionTracker::CAPACITY_SECONDS - 1);
|
|
$this->assertSame(3, (int) ConnectionTracker::capacity()[2]['online_clients'], 'older: counted now');
|
|
$this->assertSame(3, (int) json_decode((string) file_get_contents($rFile), true)[2]['online_clients'], 'and left for the next viewer');
|
|
} finally {
|
|
[$GLOBALS['rSettings'], $GLOBALS['rServers']] = $rWas;
|
|
DatabaseFactory::reset();
|
|
@unlink($rFile);
|
|
}
|
|
}
|
|
}
|