mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-05 20:02:41 +02:00
A daemon-served TS viewer's row is written with pid 0 — the PHP worker that admitted it returns at the X-Accel hand-off. closeConnection() only killed a pid above 0 and then deleted the row, so a limit eviction or an admin kick left the viewer streaming from the daemon, untracked: a line limited to one connection could hold any number of TS streams. - FanoutClient::dropConnection() calls the daemon's new DELETE /connections/<uuid> (XC_VM_Fanout, feature "drop_connection"). - ConnectionTracker::dropDaemonViewer() drops a pid-0 viewer directly, or sends a drop_con signal to the viewer's node (DB and Redis signal paths, handled by SignalsCommand). Both closeConnection() implementations use it. - The limiter spares the requesting connection by uuid: every daemon row shares pid 0, so the old "not my pid" check let a new viewer evict itself. HMAC identities in Redis mode are looked up under their "<hmac>_<identifier>" key (a null line id hit an int-typed parameter). - A kicked local HLS viewer loses its segment marker at once, and in Redis mode a closed HLS connection is no longer silently reopened by the next playlist request (the MySQL path already required hls_end = 0). A re-auth that reuses the player's deterministic HLS uuid leaves ENDED / replaces the closed row, so the reaper cannot delete the new connection. - fanout_sync also reconciles the other way: a daemon viewer whose row is gone past a 20 s grace is dropped. It is skipped when the rows could not be read, so a Redis/DB blip cannot disconnect everyone. - Redis signal keys include the payload, so pid-less signals do not overwrite each other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
48 lines
1.9 KiB
PHP
48 lines
1.9 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use XcVm\Cli\Commands\FanoutSyncCommand;
|
|
|
|
/**
|
|
* @covers \XcVm\Cli\Commands\FanoutSyncCommand::dropOrphans
|
|
*
|
|
* The reverse of fanout_sync's reconcile: a daemon viewer whose connection row is
|
|
* gone (kicked, reaped, expired line) is dropped once it has stayed orphaned for
|
|
* the grace period — and never one that still has an open row.
|
|
*/
|
|
final class FanoutSyncOrphanTest extends TestCase {
|
|
|
|
public function testOrphanIsDroppedOnlyAfterTheGrace(): void {
|
|
$rSync = new FanoutSyncCommand();
|
|
$rDropped = [];
|
|
$rDrop = function (string $rUUID) use (&$rDropped) {
|
|
$rDropped[] = $rUUID;
|
|
};
|
|
$rRows = [['uuid' => 'kept', 'hls_end' => 0]];
|
|
|
|
$this->assertSame([], $rSync->dropOrphans(['kept', 'orphan'], $rRows, 1000, $rDrop), 'first sighting: grace starts');
|
|
$this->assertSame([], $rSync->dropOrphans(['kept', 'orphan'], $rRows, 1010, $rDrop), 'still within the grace');
|
|
$this->assertSame(['orphan'], $rSync->dropOrphans(['kept', 'orphan'], $rRows, 1020, $rDrop));
|
|
$this->assertSame(['orphan'], $rDropped, 'the viewer with a row is never dropped');
|
|
}
|
|
|
|
public function testAViewerWhoseRowReturnsIsForgiven(): void {
|
|
$rSync = new FanoutSyncCommand();
|
|
$rDrop = function () {
|
|
};
|
|
$rSync->dropOrphans(['u'], [], 1000, $rDrop);
|
|
// The row reappears (a read racing its write): the grace resets.
|
|
$this->assertSame([], $rSync->dropOrphans(['u'], [['uuid' => 'u', 'hls_end' => 0]], 1015, $rDrop));
|
|
$this->assertSame([], $rSync->dropOrphans(['u'], [], 1025, $rDrop), 'orphaned again: a fresh grace');
|
|
}
|
|
|
|
public function testAClosedRowCountsAsGone(): void {
|
|
$rSync = new FanoutSyncCommand();
|
|
$rDrop = function () {
|
|
};
|
|
$rClosed = [['uuid' => 'k', 'hls_end' => 1]]; // kicked: closed, not yet reaped
|
|
$rSync->dropOrphans(['k'], $rClosed, 1000, $rDrop);
|
|
$this->assertSame(['k'], $rSync->dropOrphans(['k'], $rClosed, 1020, $rDrop));
|
|
}
|
|
}
|