Skip to content

ThrottlingRequestManager does not stop a 429 from retiring the session #2143

Description

@janbuchar

_raise_for_session_blocked_status_code records the domain delay and then falls through to
Session.is_blocked_status_code, and 429 is in _DEFAULT_BLOCKED_STATUS_CODES. So on a domain the
throttler covers, a 429 still raises SessionError, still retires the session and still spends a
max_session_rotations slot — the churn #1437 was filed about. Only ignore_http_error_status_codes
avoids it, and nothing says so.

This wants fixing together with a stall timeout, in one PR:

  • Nothing bounds a permanently rate-limiting domain except the retry accounting the fix removes.
    _should_retry_request caps SessionError at max_session_rotations and refuses
    HttpClientStatusCodeError outright, so today such a domain drains and the crawl ends. Make a
    throttled 429 free and it never terminates — run() hangs while the statistics report nothing,
    which is what the TS port walked into.
  • So the fix also needs a per-domain stall timeout that gives up once a domain with queued requests
    has been rate-limiting for that long, and a keep_alive exemption. max_domain_stall_secs is a
    constructor argument, so landing it separately ships a release where the knob does nothing.

Both halves are in apify/crawlee#3741. From #1762.

Activity

  1. added
    bugSomething isn't working.
    t-toolingIssues with this label are in the ownership of the tooling team.
    on Aug 9, 2026
  2. self-assigned this
    on Aug 10, 2026
  3. chenhz01 commented on Sep 22, 2026

    @chenhz01

    One decision is split across two layers: _raise_for_session_blocked_status_code records the domain delay, then falls through to Session.is_blocked_status_code, where 429 sits in _DEFAULT_BLOCKED_STATUS_CODES — so the throttler's mitigation is instantly undone by a retirement that burns a max_session_rotations slot, the exact churn #1437 complains about. The clean fix is a three-state verdict per response: retry-in-place (throttler owns it: record delay, retry same session), rotate-session, or fail. Only statuses the throttler cannot absorb should ever reach the session layer — and because a domain that rate-limits forever would then retry in place indefinitely, this needs the per-domain stall timeout the report calls for (max_domain_stall_secs plus the keep_alive exemption), in the same PR:

    if throttler.handles(status): await throttler.backoff(); return RETRY_SAME_SESSION

    This decision split is a core case in my published resilient-agent-toolkit. Want me to take a crack at it? Happy to open a PR with working code.

  4. Mantisus commented on Sep 22, 2026

    @Mantisus
    Collaborator

    Hi @chenhz01, thanks for taking a look at this

    Want me to take a crack at it? Happy to open a PR with working code.

    I'm sorry, but this task involves many important components. It will be implemented by one of the core maintainers.

  5. added a commit that references this issue on Sep 29, 2026
    6b7db66
  6. vdusek commented on Sep 29, 2026

    @vdusek
    Collaborator

    resolved by #2250

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working.t-toolingIssues with this label are in the ownership of the tooling team.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions