Conversation
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes #407.
Sonarr's indexer test times out after 100s. The Quasarr logs from the same reproduction show why:
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_getre-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 ofSEARCH_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,2040wait twice as long as a single category.get_search_resultstakes an optionaldeadlineand 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_idsandget_wanted_episodeswalk 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 optionaldeadline, 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_titletakes 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
Sourceinstance. 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 returnsNoneon a failed request instead of an empty page, and the helpers report through astatusdict whether the walk completed.start_timeis 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)tests/test_search_fanout_deadline.pycovers 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.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_getraising once the budget callable reports it is spent.tests/test_mx_source.py: the budget is clamped to the fan-out deadline under slow-mode settings.tests/test_radarr_wanted.pyandtests/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.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.tests/test_mx_source.py: seed acquisition that returns nothing, or that did not report a completed walk, keeps the previous resume offset.start_timeinstead of0.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