Deep-review round 3 of bug hunt 2026-09. - The YouTube and Plex progress bodies are bounded by POSITION_MAX_S (the int4 position columns): a finite position of 2^31 or more was a DataError, i.e. a 500 — the sibling of round 2's non-finite fix. - plex/stream.py promised "a periodic reaper", but reap_idle ran only inside the NEXT session request: after the last playback an abandoned session (its ffmpeg still working, its directory a whole segmented copy of the film) lived until someone pressed play again. A lifespan task reaps every 60 s. - Tests for what round 2 left untested: the 422's body keeps the list shape the UI localizes, and stream_session refuses non-finite start/aoff.
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
"""Idle HLS sessions are reaped on a timer, not only when the next session is requested.
|
|
|
|
plex/stream.py promises "a periodic reaper kills idle ones", but `reap_idle` ran only inside
|
|
POST /session: after the last playback, an abandoned session (its ffmpeg still working through the
|
|
film, its directory a whole segmented copy of it) lived until somebody pressed play again.
|
|
"""
|
|
|
|
import asyncio
|
|
|
|
import app.main as app_main
|
|
from app.main import app
|
|
|
|
|
|
class _Configured:
|
|
def __enter__(self):
|
|
return None
|
|
|
|
def __exit__(self, *exc):
|
|
return False
|
|
|
|
|
|
def test_idle_sessions_are_reaped_while_nobody_starts_a_new_one(monkeypatch):
|
|
async def _no_sweep():
|
|
return None
|
|
|
|
reaped: list[int] = []
|
|
monkeypatch.setattr(app_main, "SessionLocal", _Configured)
|
|
monkeypatch.setattr(app_main.state, "is_configured", lambda db: True)
|
|
monkeypatch.setattr(app_main, "_sweep_hls_orphans", _no_sweep)
|
|
monkeypatch.setattr(app_main, "start_scheduler", lambda: None)
|
|
monkeypatch.setattr(app_main, "shutdown_scheduler", lambda: None)
|
|
monkeypatch.setattr(app_main.plex_subtitles, "shutdown", lambda: None)
|
|
monkeypatch.setattr(app_main, "_HLS_REAP_EVERY_S", 0.01)
|
|
monkeypatch.setattr(app_main.plex_stream, "reap_idle", lambda: reaped.append(1) or 0)
|
|
|
|
async def _cycle():
|
|
async with app_main.lifespan(app):
|
|
await asyncio.sleep(0.2)
|
|
|
|
asyncio.run(_cycle())
|
|
assert len(reaped) >= 2, "no periodic reap ran while the app was up"
|