diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-20 07:18:16 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-20 07:18:16 +0300 |
| commit | f45737298d9536819c094ab90917db362b28332a (patch) | |
| tree | fc55a9e57cfb7bd66d7512df02614e54716de79e /player-server | |
| parent | 190bc9ecfb2e6026d0ff831b89dcc2a39843775d (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.go | 30 |
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 { |
