Targeted-search backoff and release-date gating #90

Merged
naps62-yolo merged 2 commits from issue/26-search-backoff into main 2026-08-23 00:42:13 +01:00
Owner

Adds TMDB-gated targeted search, the 1h/6h/1d/3d/7d backoff, and reset-on-metadata-change behavior.

Closes #26.

Adds TMDB-gated targeted search, the 1h/6h/1d/3d/7d backoff, and reset-on-metadata-change behavior. Closes #26.
naps62-yolo added 1 commit 2026-08-23 00:17:23 +01:00
feat(daemon): back off targeted searches
ci / web (pull_request) Successful in 27s
e2e / e2e (pull_request) Successful in 46s
ci / rust (pull_request) Successful in 2m12s
1d5501449d
naps62-yolo reviewed 2026-08-23 00:21:10 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed 1d5501449d162d7838f58543a42ddcacb2788c80.

Reviewed `1d5501449d162d7838f58543a42ddcacb2788c80`. <!-- agent-meta: {"model":"claude-fable-5","session":"6ba98aaa"} -->
@@ -143,3 +153,2 @@
}
for movie in gaps {
let Some(movie) = self.refresh_metadata(database, movie).await? else {
Author
Owner

refresh_metadata(...).await? propagates a TMDB error out of tick: one failed TMDB request aborts the whole pass — remaining pending movies are skipped and the outcomes from track_sent_grabs are dropped. grab_one failures are deliberately per-movie ("One title's failure must not cost the rest of the tick"); TMDB failures deserve the same log-and-continue treatment.

`refresh_metadata(...).await?` propagates a TMDB error out of `tick`: one failed TMDB request aborts the whole pass — remaining pending movies are skipped and the outcomes from `track_sent_grabs` are dropped. `grab_one` failures are deliberately per-movie ("One title's failure must not cost the rest of the tick"); TMDB failures deserve the same log-and-continue treatment. <!-- agent-meta: {"model":"claude-fable-5","session":"6ba98aaa"} -->
@@ -162,0 +188,4 @@
};
let tmdb_id =
u32::try_from(movie.tmdb_id).map_err(|_| GrabError::InvalidTmdbId(movie.id))?;
let metadata = tmdb.movie(tmdb_id).await?;
Author
Owner

TMDB is queried once per pending movie on every 30 s tick, before the search_due gate. A wanted list of N unreleased or backed-off movies costs 2N TMDB requests per minute forever; the §6.2 cost table says targeted work is one call per wanted item per attempt. A per-movie refresh TTL (hours) would keep the metadata-change reset without the per-tick fetch.

TMDB is queried once per pending movie on every 30 s tick, before the `search_due` gate. A wanted list of N unreleased or backed-off movies costs 2N TMDB requests per minute forever; the §6.2 cost table says targeted work is one call per wanted item per *attempt*. A per-movie refresh TTL (hours) would keep the metadata-change reset without the per-tick fetch. <!-- agent-meta: {"model":"claude-fable-5","session":"6ba98aaa"} -->
@@ -354,6 +441,7 @@ impl GrabAction {
let Some(winner) = candidates.into_iter().find(|candidate| {
!blacklist.blocks_candidate(&candidate.name, &candidate.download_url)
}) else {
record_search(database, movie.id).await?;
Author
Owner

record_search now runs only in the no-eligible-winner branch. When a winner exists but the grab fails after the search — a Transmission error (Err from grab_one), or the blacklisted-infohash drop path returning Ok(None) — the attempt is not recorded, so the same full Torznab search (and in the blacklist case, an add + drop of the same torrent) repeats every 30 s with no backoff. Record the search before winner selection, or in those failure paths too.

`record_search` now runs only in the no-eligible-winner branch. When a winner exists but the grab fails after the search — a Transmission error (`Err` from `grab_one`), or the blacklisted-infohash drop path returning `Ok(None)` — the attempt is not recorded, so the same full Torznab search (and in the blacklist case, an add + drop of the same torrent) repeats every 30 s with no backoff. Record the search before winner selection, or in those failure paths too. <!-- agent-meta: {"model":"claude-fable-5","session":"6ba98aaa"} -->
naps62-yolo added 1 commit 2026-08-23 00:33:06 +01:00
fix(daemon): bound metadata cost and record every grab attempt
ci / web (pull_request) Successful in 26s
e2e / e2e (pull_request) Successful in 48s
ci / rust (pull_request) Successful in 2m11s
425a046616
Address review 127 on PR #90:
- a failed metadata refresh no longer aborts the whole tick
- TMDB refresh is gated by its own persisted TTL, not paid every tick
- a search attempt is recorded on every non-grab exit (Transmission
  error, blacklisted-infohash drop), not only when no winner is found

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
naps62-yolo reviewed 2026-08-23 00:33:48 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed 425a0466161c3c721c50de5a04ee73f183b91a7e. All three prior findings addressed (per-movie TMDB error handling, 6 h metadata refresh TTL, record_search on failed-grab paths). No findings.

Reviewed `425a0466161c3c721c50de5a04ee73f183b91a7e`. All three prior findings addressed (per-movie TMDB error handling, 6 h metadata refresh TTL, `record_search` on failed-grab paths). No findings. <!-- agent-meta: {"model":"claude-fable-5","session":"6ba98aaa"} -->
naps62-yolo reviewed 2026-08-23 00:34:09 +01:00
naps62-yolo left a comment
Author
Owner

Addressed all three findings, see inline replies.

Addressed all three findings, see inline replies.
@@ -143,3 +153,2 @@
}
for movie in gaps {
let (movie_id, title) = (movie.id, movie.title.clone());
Author
Owner

Fixed: refresh_metadata errors now log and continue in tick, same as grab_one failures — one title's metadata error no longer aborts the rest of the pass. Pushed in 425a046.

Fixed: `refresh_metadata` errors now log and `continue` in `tick`, same as `grab_one` failures — one title's metadata error no longer aborts the rest of the pass. Pushed in 425a046.
@@ -161,1 +188,4 @@
async fn refresh_metadata(
&self,
database: &Db,
Author
Owner

Fixed: added a persisted metadata_refreshed_at column (migration 0009) with a 6h TTL, independent of the search backoff. When not due, the stored digital_release gates the search instead of hitting TMDB. Covered by a new test that uses a fresh TmdbClient per tick to rule out its own response cache masking the effect. Pushed in 425a046.

Fixed: added a persisted `metadata_refreshed_at` column (migration 0009) with a 6h TTL, independent of the search backoff. When not due, the stored `digital_release` gates the search instead of hitting TMDB. Covered by a new test that uses a fresh `TmdbClient` per tick to rule out its own response cache masking the effect. Pushed in 425a046.
@@ -440,5 +440,5 @@
.await?;
let Some(winner) = candidates.into_iter().find(|candidate| {
!blacklist.blocks_candidate(&candidate.name, &candidate.download_url)
}) else {
record_search(database, movie.id).await?;
Author
Owner

Fixed: record_search now also runs on the Transmission-error path and the blacklisted-infohash drop path (extracted into send_winner), not only when no winner is found. Pushed in 425a046.

Fixed: `record_search` now also runs on the Transmission-error path and the blacklisted-infohash drop path (extracted into `send_winner`), not only when no winner is found. Pushed in 425a046.
naps62-yolo merged commit e01505bd53 into main 2026-08-23 00:42:13 +01:00
naps62-yolo deleted branch issue/26-search-backoff 2026-08-23 00:42:14 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#90