summaryrefslogtreecommitdiff
path: root/player-server
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-20 07:18:16 +0300
committerPaul Buetow <paul@buetow.org>2026-05-20 07:18:16 +0300
commitf45737298d9536819c094ab90917db362b28332a (patch)
treefc55a9e57cfb7bd66d7512df02614e54716de79e /player-server
parent190bc9ecfb2e6026d0ff831b89dcc2a39843775d (diff)
Log setFeedBackoff UPDATE errors instead of swallowing (w9)
Silently discarding UPDATE failures left the per-feed consecutive_failures counter stale, so a transient DB error caused the checker to keep hammering the failing feed every interval. setFeedBackoff now returns the error and callers log it at warn level so operators see the failure. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'player-server')
-rw-r--r--player-server/internal/service/podcast_checker.go30
1 files changed, 23 insertions, 7 deletions
diff --git a/player-server/internal/service/podcast_checker.go b/player-server/internal/service/podcast_checker.go
index 04c7bc3..c63c285 100644
--- a/player-server/internal/service/podcast_checker.go
+++ b/player-server/internal/service/podcast_checker.go
@@ -69,8 +69,13 @@ func (s *podcastFeedChecker) checkFeed(ctx context.Context, feed model.PodcastFe
if err != nil {
// Backoff bookkeeping happens inside fetchFeedWithRetry for the
// host tracker; we still bump the per-feed consecutive_failures so
- // the existing feed-level scheduling honours the failure.
- s.setFeedBackoff(ctx, &feed)
+ // the existing feed-level scheduling honours the failure. Log any
+ // UPDATE failure at warn level: silently swallowing it would leave
+ // the backoff counter stale and the checker would keep retrying the
+ // failing feed at every interval.
+ if berr := s.setFeedBackoff(ctx, &feed); berr != nil {
+ s.logger.Warn("podcast: failed to record feed backoff", "feed_id", feed.ID, "url", feed.FeedURL, "err", berr)
+ }
return err
}
defer resp.Body.Close()
@@ -86,18 +91,24 @@ func (s *podcastFeedChecker) checkFeed(ctx context.Context, feed model.PodcastFe
return nil
}
if resp.StatusCode != http.StatusOK {
- s.setFeedBackoff(ctx, &feed)
+ if berr := s.setFeedBackoff(ctx, &feed); berr != nil {
+ s.logger.Warn("podcast: failed to record feed backoff", "feed_id", feed.ID, "url", feed.FeedURL, "err", berr)
+ }
return fmt.Errorf("feed check status %d", resp.StatusCode)
}
parsed, err := s.parseFeedReader(resp.Body)
if err != nil {
- s.setFeedBackoff(ctx, &feed)
+ if berr := s.setFeedBackoff(ctx, &feed); berr != nil {
+ s.logger.Warn("podcast: failed to record feed backoff", "feed_id", feed.ID, "url", feed.FeedURL, "err", berr)
+ }
return err
}
if err := s.updateFeedFromParsed(ctx, &feed, parsed, resp.Header.Get("ETag")); err != nil {
- s.setFeedBackoff(ctx, &feed)
+ if berr := s.setFeedBackoff(ctx, &feed); berr != nil {
+ s.logger.Warn("podcast: failed to record feed backoff", "feed_id", feed.ID, "url", feed.FeedURL, "err", berr)
+ }
return err
}
@@ -118,7 +129,12 @@ func (s *podcastFeedChecker) checkFeed(ctx context.Context, feed model.PodcastFe
return nil
}
-func (s *podcastFeedChecker) setFeedBackoff(ctx context.Context, feed *model.PodcastFeed) {
+// setFeedBackoff bumps the feed's consecutive_failures counter, computes the
+// next exponential-backoff check time, and persists the updated row. It now
+// returns the UPDATE error instead of swallowing it: callers log at warn level
+// so a persistent DB failure is visible to operators (otherwise the counter
+// never advances and the checker hammers the failing feed every interval).
+func (s *podcastFeedChecker) setFeedBackoff(ctx context.Context, feed *model.PodcastFeed) error {
feed.ConsecutiveFailures++
backoff := baseFeedRetryBackoff * (1 << max(0, feed.ConsecutiveFailures-1))
if backoff > maxFeedRetryBackoff {
@@ -126,7 +142,7 @@ func (s *podcastFeedChecker) setFeedBackoff(ctx context.Context, feed *model.Pod
}
next := s.clock.Now().Add(backoff)
feed.NextCheckAt = &next
- _ = s.store.UpdateFeed(ctx, feed)
+ return s.store.UpdateFeed(ctx, feed)
}
func (s *podcastFeedChecker) updateFeedFromParsed(ctx context.Context, feed *model.PodcastFeed, parsed *podcast.ParsedFeed, etag string) error {