fix(sync): only a refusal about the channel spends its fallback window

A failed connection or token step answered nothing about the channel, yet stamped it: one proxy blip
during an RSS blackout delayed every due channel by a whole window. YouTubeError now names the
endpoint that answered; a failure no endpoint gave, or a spent quota, ends the round and stamps
nothing.
This commit is contained in:
2026-09-19 05:54:19 +02:00
parent 038da5374f
commit a7891d03c5
3 changed files with 27 additions and 12 deletions
+8 -6
View File
@@ -197,9 +197,10 @@ def run_feed_fallback(db: Session, channel_ids: list[str]) -> dict[str, int]:
uploads playlist is 1 unit, so a whole day of total outage costs 12 units per channel at the
default 2 hours; it yields to the backfill reserve like every other background read. The clock is
`last_rss_at` or `feed_fallback_at`, whichever is later, so a channel is read at most once per
window whether its feed came back in between or not. A FAILED read stamps the clock too: a
terminated channel's uploads playlist answers 404 for good, and without the stamp it would be
charged again on every 20-minute poll instead of once per window."""
window whether its feed came back in between or not. A read the API REFUSED stamps the clock
too: a terminated channel's uploads playlist answers 404 for good, and without the stamp it
would be charged again on every 20-minute poll instead of once per window. A failure that is not
about the channel (no connection, no token, the quota spent) stamps nothing and ends the round."""
hours = sysconfig.get_int(db, "rss_fallback_hours")
if hours <= 0 or not channel_ids:
return {"read": 0, "new": 0, "failed": 0}
@@ -235,10 +236,11 @@ def run_feed_fallback(db: Session, channel_ids: list[str]) -> dict[str, int]:
# already logged the call's own failure (`youtube.api.error`).
except YouTubeError as exc:
failed += 1
_stamp_fallback_attempt(db, channel.id)
# Out of quota upstream: every further channel would be charged the same refusal.
if is_quota_refusal(exc):
# Not an answer about THIS channel — no connection, no token, no quota: every
# further channel would fail the same way, and none of them has used its window.
if exc.path != "playlistItems" or is_quota_refusal(exc):
break
_stamp_fallback_attempt(db, channel.id)
continue
rows = [
s