fix(plex): playback sessions per viewer, bounded in total; a fair share of subtitle passes
HLS sessions were keyed by the item, so a second account opening the same film killed the first one's session; the displaced player reclaimed at its playhead and evicted the other in turn. The registry is now keyed by (viewer, rating_key), segment requests resolve by both, and the session directory carries the viewer (the orphan sweep still recognises the older shapes). Only transcodes counted toward a cap, so remuxes — each a whole segmented copy of the film on the scratch volume — were bounded by the library size. Three bounds now apply after every start, each dropping the least recently used: two sessions per account, the transcode cap, and a new admin knob, plex_max_sessions (default 8), over every session. A superseded session request returns at once, since it waits on the killed ffmpeg, so an account cannot park more request workers than it may hold sessions. The subtitle extractor's full-pass slots were global and minutes long each; one account opening subtitles on film after film could hold them all. An account may now hold three of the six, and still gets windows past that.
This commit is contained in:
@@ -690,8 +690,8 @@ def test_a_scrubbing_viewer_cannot_queue_a_window_per_stop(monkeypatch):
|
||||
|
||||
def test_nothing_more_is_queued_while_the_extractor_is_saturated(monkeypatch):
|
||||
pool = _manual_pool(monkeypatch)
|
||||
for rk in range(20):
|
||||
subtitles.ensure(str(rk), 0, at=0.0)
|
||||
for rk in range(20): # twenty viewers, so no one account's share is what stops the passes
|
||||
subtitles.ensure(str(rk), 0, at=0.0, viewer=rk)
|
||||
windows = [a for _, a in pool.jobs if a[3] is not None]
|
||||
assert len(windows) == subtitles._MAX_WINDOWS_INFLIGHT
|
||||
assert len(pool.jobs) - len(windows) == subtitles._MAX_FULL_INFLIGHT
|
||||
@@ -971,6 +971,27 @@ def test_a_window_is_not_refused_while_full_passes_hold_their_slots(monkeypatch)
|
||||
assert full.jobs == [], "past its bound no full pass is queued"
|
||||
|
||||
|
||||
def test_one_account_cannot_hold_every_full_pass_slot(monkeypatch):
|
||||
"""The full-pass slots are global and minutes long each: one account opening subtitles on film
|
||||
after film held them all, and nobody else's track ever completed. Past its share an account
|
||||
still gets windows — what makes a track watchable now — and the next account still gets a pass."""
|
||||
win, full = _ManualPool(), _ManualPool()
|
||||
monkeypatch.setattr(subtitles, "_WIN_POOL", win)
|
||||
monkeypatch.setattr(subtitles, "_FULL_POOL", full)
|
||||
monkeypatch.setattr(subtitles, "_extract_and_publish", lambda *a: None)
|
||||
for rk in range(subtitles._MAX_FULL_PER_USER + 1):
|
||||
subtitles.ensure(str(1000 + rk), 1, at=600.0, viewer=7)
|
||||
assert len(full.jobs) == subtitles._MAX_FULL_PER_USER
|
||||
assert len(win.jobs) == subtitles._MAX_FULL_PER_USER + 1, (
|
||||
"the account over its share still gets windows"
|
||||
)
|
||||
subtitles.ensure("2000", 1, at=600.0, viewer=8)
|
||||
assert len(full.jobs) == subtitles._MAX_FULL_PER_USER + 1, "another account is not shut out"
|
||||
full.run_all() # the account's passes end: its share is free again
|
||||
subtitles.ensure("3000", 1, at=600.0, viewer=7)
|
||||
assert any(a[1] == "3000" and a[3] is None for _, a in full.jobs)
|
||||
|
||||
|
||||
def test_a_poll_after_shutdown_queues_nothing(monkeypatch):
|
||||
pool = _manual_pool(monkeypatch)
|
||||
subtitles.shutdown()
|
||||
|
||||
Reference in New Issue
Block a user