fix(r5): address the third /code-review high round — 7 findings

- the sweep guard anchors the rating_key to digits: the round-2 widening had
  degenerated to "anything ending in _<digits>" (backup_2024, release_10)
- RSS counts apply-side failures too, so a read-only database no longer reports
  a wholly failed poll as "ok — new=0, failed=0"
- the player reads/writes its volume directly (readAccount/writeAccount): the
  persisted-object hook rendered nothing and cost a second write per settle
- new tests: the sweep guard's accept/reject table, and run_ffmpeg's threading
  (drain, cancel while silent, stall watchdog, stderr tail on a nonzero exit)
- the breaker sentinel is checked one way (isinstance) at all four sites
- run_rss_poll is typed dict[str, int]

All three new test groups were mutation-checked: reverting each fix turns them
red (or, for the cancel/stall pair, hangs — which is the bug itself).
This commit is contained in:
2026-07-24 00:17:09 +02:00
parent 53ff61653d
commit c0db80d333
7 changed files with 271 additions and 35 deletions
+9 -6
View File
@@ -346,10 +346,11 @@ def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
created = 1
# Brand-new playlist: just append every video in order.
for vid in desired:
if breaker.run(
added = breaker.run(
lambda v=vid: yt.add_playlist_item(pl.yt_playlist_id, v),
lambda v=vid: failures.append(v),
) is not FAILED:
)
if not isinstance(added, _Failed):
inserted += 1
if not failures:
pl.synced_fingerprint = fingerprint(pl.name, desired)
@@ -382,10 +383,11 @@ def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
# Delete extras (local is authoritative).
for it in current:
if it["video_id"] not in desired_set:
if breaker.run(
dropped = breaker.run(
lambda i=it: yt.delete_playlist_item(i["item_id"]),
lambda i=it: failures.append(i["video_id"]),
) is not FAILED:
)
if not isinstance(dropped, _Failed):
deleted += 1
# Insert missing (append; the reorder pass below fixes positions).
cur_set = set(cur_vids)
@@ -407,12 +409,13 @@ def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
continue # failed insert; skip
if i < len(model) and model[i] == want:
continue
if breaker.run(
moved = breaker.run(
lambda w=want, pos=i: yt.move_playlist_item(
item_id_by_vid[w], pl.yt_playlist_id, w, pos
),
lambda w=want: failures.append(w),
) is FAILED:
)
if isinstance(moved, _Failed):
continue
reordered += 1
j = model.index(want)
+13 -6
View File
@@ -48,11 +48,15 @@ def get_service_user(db: Session) -> User | None:
return users[0] if users else None
def run_rss_poll(db: Session, channels: list[Channel] | None = None) -> dict:
"""Poll every channel's free RSS feed. Returns `{"new": inserted, "failed": unreadable_feeds}`
— the failure count is part of the RESULT, not just a log line, so the scheduler card and the
audit summary can tell "no new uploads" apart from "every feed was unreachable" (an egress
outage otherwise renders identically to a quiet hour)."""
def run_rss_poll(db: Session, channels: list[Channel] | None = None) -> dict[str, int]:
"""Poll every channel's free RSS feed. Returns `{"new": inserted, "failed": channels_that_did
_not_complete}` — the failure count is part of the RESULT, not just a log line, so the scheduler
card and the audit summary can tell "no new uploads" apart from "nothing worked" (an egress
outage, or a database that can't accept writes, otherwise renders identically to a quiet hour).
`failed` counts a channel whose feed could not be READ *or* whose rows could not be APPLIED —
both leave the channel un-polled, and counting only the first would move the blind spot rather
than remove it."""
if channels is None:
channels = db.execute(select(Channel)).scalars().all()
total = len(channels)
@@ -87,12 +91,15 @@ def run_rss_poll(db: Session, channels: list[Channel] | None = None) -> dict:
try:
new += apply_rss_feed(db, channel, entries)
except Exception:
# A write failure (read-only DB, failover) leaves the channel just as un-polled as
# an unreachable feed — count it, or a wholly broken run still reports "failed=0".
db.rollback()
failed += 1
log.exception("RSS apply failed for channel %s", channel.id)
done += 1
progress.report(done, total, "rss_poll")
if failed:
log.warning("RSS poll: %d/%d channel feeds could not be read", failed, total)
log.warning("RSS poll: %d/%d channels could not be polled", failed, total)
return {"new": new, "failed": failed}