Skip to content

fix(renderer): give chrome_proxy recovery arm its own budget and load-shed it#321

Merged
us merged 1 commit into
mainfrom
fix/arm-skip-promoted-host
Jul 18, 2026
Merged

fix(renderer): give chrome_proxy recovery arm its own budget and load-shed it#321
us merged 1 commit into
mainfrom
fix/arm-skip-promoted-host

Conversation

@us

@us us commented Jul 18, 2026

Copy link
Copy Markdown
Owner

The residential-egress recovery arm never fired on real /v1/scrape hard-blocks. Prod-measured with a temporary diagnostic: the HTTP + LightPanda + Chrome ladder burns a ~15s scrape deadline down to ~2s before the arm (ARM_DIAG gate ... remaining_ms=1984 arm_floor_ms=8000), so the deadline.remaining() >= 8s floor was never met and chrome_proxy never fetched. Wikipedia's ~12/13 recovery was the HTTP-tier residential proxy retry, not the arm.

Fix

  • Fresh budget: dispatch the arm with Deadline::now_plus(CHROME_PROXY_ARM_BUDGET_MS = 12s) instead of the exhausted shared deadline (effective render budget min(this, chrome_nav_budget_ms), both 12s). Only hard-blocked scrapes pay it; the SaaS->engine fetch tolerates 120s (crw-client.ts TIMEOUT_MS), and the fetch overall-timeout still clamps to the fresh 12s so a hung page can't hang it.
  • Drop the floor (a no-op against a fresh deadline).
  • Load-shed (required): a non-blocking chrome_proxy_arm_sem sized to the chrome_proxy pool (config.pool_size, == its conn_semaphore). No permit -> return the block. Prevents a datacenter-blocked batch/crawl from queueing every page on the ~pool residential slots for up to the SaaS timeout and collapsing co-tenant throughput (the 500-1000-concurrent invariant). On the managed prod path chrome_proxy is arm-exclusive (!proxy_active), so a permit implies a free pool slot; a fired arm never queues.
  • Telemetry: count sheds as render_route_decision_total{armShed}.

Removes the temporary arm diagnostic.

Validation

Two review rounds (correctness + p90/concurrency plan review; correctness + adversarial code review) reached consensus. Tests: chrome_proxy_arm_fires_below_old_floor (2s deadline -> fires), chrome_proxy_arm_load_shed_when_pool_saturated (drained permits -> shed), full workspace green (1292), clippy clean lean + --features cloak.

Post-deploy: a real datacenter-blocked hard scrape the HTTP-tier retry didn't clear now fires chrome_proxy (chain includes it) and recovers; watch armShed and the hard-block p90.

…-shed it

The residential-egress recovery arm never fired on real /v1/scrape
hard-blocks: the HTTP + LightPanda + Chrome ladder burns a ~15s scrape
deadline down to ~2s before the arm, far below the 8s floor, so the gate
`deadline.remaining() >= floor` was never satisfied (prod-measured ~2s).

- dispatch the arm with a fresh `Deadline::now_plus(CHROME_PROXY_ARM_BUDGET_MS
  = 12s)` instead of the exhausted shared deadline; effective render budget is
  `min(this, chrome_nav_budget_ms)`. Only hard-blocked scrapes (which would
  otherwise fail) pay it, and the SaaS->engine fetch tolerates 120s.
- drop the `deadline.remaining()` floor (a no-op against a fresh deadline) and
  the CHROME_PROXY_ARM_FLOOR_MS const.
- load-shed: a non-blocking `chrome_proxy_arm_sem` (sized to the chrome_proxy
  pool) gates firing so a burst of datacenter-blocked URLs can never queue on
  the residential pool for up to the SaaS timeout and collapse co-tenant
  throughput; no permit -> return the block (best-effort recovery).
- count sheds as render_route_decision_total{armShed} so an undersized
  residential pool is observable under sustained hard-block load.

Also removes the temporary arm budget diagnostic.
@us
us merged commit 1a3942b into main Jul 18, 2026
10 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 18, 2026
@us
us deleted the fix/arm-skip-promoted-host branch July 18, 2026 17:21
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant