Skip to content

Cap search fan-out and MX feed runs by wall clock - #435

Merged
rix1337 merged 1 commit into
mainfrom
dev
Aug 10, 2026
Merged

Cap search fan-out and MX feed runs by wall clock#435
rix1337 merged 1 commit into
mainfrom
dev

Conversation

@rix1337

@rix1337 rix1337 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Fixes #407.

Sonarr's indexer test times out after 100s. The Quasarr logs from the same reproduction show why:

21:24:19 Providing releases 1-4265 ... [DJ DT DW FX HE HS MB NK SF SJ SL WX] Time taken: 13.92 seconds
21:30:31 Providing releases 1-4265 ... [DJ DT DW FX HE HS MB MX NK SJ SL WX] Time taken: 314.48 seconds

The badge bar lists the sources that actually ran (the rest were served from cache). The slow runs are the ones where MX ran.

MX has no discovery endpoint, so its feed seeds from the *arr wanted lists and queries the source per seed, up to FEED_LIBRARY_LIMIT (50). Each seed costs several sequential round trips, so a full run took roughly 5 minutes. Nothing bounded it, and nothing bounded the fan-out either, so one slow source held the whole Newznab response open until Sonarr gave up and disabled the indexer.

Changes

MX's feed loop now stops at a wall-clock budget of FEED_REQUEST_TIMEOUT_SECONDS, the same pattern FF already uses. The budget is handed down as a callable that _get re-reads before every round trip, so a seed with many links cannot outrun the budget it started with; a seed cut short is left for the next run. Because a bounded run rarely reaches every seed, it resumes at the seed the previous run stopped on (tracked per base category) so successive pulls still cover the whole wanted list instead of replaying the same head.

SearchExecutor.run_all() gained a hard ceiling of SEARCH_FANOUT_DEADLINE_SECONDS (60s, under Radarr's and Sonarr's 100s default). Sources still running when it passes get a yellow badge and are dropped from that response. This is deliberately outside slow mode: the *arr timeout does not grow with it. Sources still have to bound their own work, since a dropped source contributes nothing.

The pool is now sized to one worker per dispatched source. The default is derived from the CPU count, so on a small host the sources dispatched last would have sat in the queue and been dropped by the deadline without ever having started, always the same ones given the fixed registry order.

Once the deadline has passed, neither IMDb metadata warming nor source dispatch runs at all. Submitting anyway would only detach a worker whose result the response cannot use, and hit the source a second time for it.

The deadline is absolute and owned by the request, not by the executor. Cache-sharing categories run one after another, so a per-run deadline would have let cat=2000,2040 wait twice as long as a single category. get_search_results takes an optional deadline and the Newznab handler computes one per request.

MX's budget is min(FEED_REQUEST_TIMEOUT_SECONDS, SEARCH_FANOUT_DEADLINE_SECONDS). Slow mode triples the feed timeout to 90s while the response window stays at 60s, and a run dropped by the fan-out has still advanced its resume offset, so those seeds would not come round again until the next full rotation.

MX's wanted-list acquisition shares the feed budget too. get_wanted_imdb_ids and get_wanted_episodes walk up to five pages per kind, each its own *arr request with its own timeout, all of it before the first budget check would have been reached. Both now take an optional deadline, stop paging at it, and clamp each page request's timeout to what is left, so the last page before the deadline cannot overrun it by the client's full 10s.

A caller timeout only ever tightens the client's own 10s, never extends it.

get_localized_title takes the same deadline, rechecked at each gate rather than snapshotted, and answers from cache only once it has passed; its 60s FlareSolverr stage is also skipped when the direct IMDb request used the rest of the budget. A seed whose title came back empty because the budget went, rather than because no title exists, keeps its place in the rotation. MX resolves a French title before it issues any request of its own, and that path can spend 30s on IMDb plus 70s on FlareSolverr, well past the budget the rest of the run obeys.

MX's resume offset is read and written under a lock, since the registry hands out one shared Source instance. It is normalized on write, and a run that acquired no seeds leaves it alone: paging stops early on both a spent budget and a failed page, so a short list can be a partial view rather than the whole thing, and a cursor normalized against it points near the head. client.wanted() now returns None on a failed request instead of an empty page, and the helpers report through a status dict whether the walk completed.

start_time is taken before IMDb metadata warming rather than after. Sources derive their own budget from it, so the later anchor let them outlive the request deadline by whatever the warming cost.

MX was the source that hit this, but it was not the only one that could. FF and DL were the only sources bounding their own fan-out by wall clock; the rest rely on per-request timeouts alone, which do not bound a loop.

Test plan

  • uv run python -X utf8 pre-commit.py (lint, format, full suite, 329 tests)
  • New: tests/test_search_fanout_deadline.py covers a source outliving the deadline (fast source's results still returned, both badged), the normal all-within-deadline path, 40 sources having to run concurrently for the run to succeed (fails against a CPU-sized pool), and two sequential runs sharing one deadline rather than restarting it.
  • New in tests/test_mx_source.py: the budget stop and resume offset across two runs, a seed cut mid-lookup being retried on the next run without marking a hostname issue, and _get raising once the budget callable reports it is spent.
  • New in tests/test_mx_source.py: the budget is clamped to the fan-out deadline under slow-mode settings.
  • New in tests/test_radarr_wanted.py and tests/test_sonarr_wanted.py: paging stops at a caller deadline, a page request carries the remaining time, and a caller timeout only tightens the client's own.
  • New tests/test_localized_title_deadline.py: an expired deadline skips the IMDb and FlareSolverr fallbacks, the deadline is rechecked after the Arr refresh rather than snapshotted on entry, and a fresh cache still answers.
  • New in tests/test_mx_source.py: seed acquisition that returns nothing, or that did not report a completed walk, keeps the previous resume offset.
  • New in both wanted-helper test files: a failed page reports partial paging, a full walk reports complete.
  • Existing MX feed tests now pass a live start_time instead of 0.0, which previously meant "budget already spent".

Not verified against the live MX API; the change is loop control around calls that were already there.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af9478038c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/__init__.py Outdated
Comment thread quasarr/search/sources/mx.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63353cba77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/__init__.py Outdated
Comment thread quasarr/search/sources/mx.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aaabcb3e57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/sources/mx.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df1ae2b5f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/__init__.py
Comment thread quasarr/providers/radarr_api.py Outdated
Comment thread quasarr/search/sources/mx.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2175e4a57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/sources/mx.py
Comment thread quasarr/providers/radarr_api.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5431549961

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/providers/imdb_metadata.py Outdated
Comment thread quasarr/search/sources/mx.py Outdated
Comment thread quasarr/search/sources/mx.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a183133d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/__init__.py
Comment thread quasarr/providers/imdb_metadata.py
Comment thread quasarr/search/sources/mx.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b16cf430c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/sources/mx.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 868fe83766

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quasarr/search/sources/mx.py
Comment thread quasarr/search/sources/mx.py Outdated
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rix1337
rix1337 merged commit ac60244 into main Aug 10, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sonarr intermittently times out when querying the Quasarr indexer

1 participant