Fixes a run that was killed at the platform timeout (TIMED_OUT) after going
idle mid-page. It had already written 16 good businesses; nothing stopped work
before the kill, so the whole run was reported as a failure.
- Never hit the platform timeout again. The actor now derives a deadline from
the run's own
timeoutAt, stops starting new work once it passes, and finishes
cleanly with whatever it collected — a SUCCEEDED run with partial results
instead of a TIMED_OUT failure. The margin scales with the window (10%,
clamped to 5-20s) so short QA validation runs are not eaten by a fixed reserve.
- A stalled listing can no longer hold the pipeline. Each business is bounded
by its own time budget; one that outlives it is abandoned with a warning rather
than holding all 15 concurrent workers open. This was the actual shape of the
failure — 4m37s of silence at 0.9% CPU, waiting on a single await that never
settled. (HTTP fetches were already bounded at 12s/attempt; the stall was in a
non-HTTP await, where the Apify client retries silently for minutes.)
- Watchdog backstop. If something hangs where no loop can observe the
deadline, a timer finalizes the run and exits 0 before the platform kills it.
- Fixed a proxy socket leak. Every request built a
ProxyAgent that was never
closed; undici holds keep-alive connections until an agent is closed, so each
request leaked one TCP connection to proxy.apify.com for the life of the run
(measured: 20 requests -> 20 sockets still open, 0 after the fix). A fresh agent
per request is kept deliberately — the residential proxy assigns an exit IP per
connection, so pooling would pin every rotated retry to the same IP.
- Bounded review pagination. Reviews with empty text are skipped, so the
"collect N reviews" loop could page forever on a business with thousands of
them. It now stops a few pages past the ideal count.
- Fix
maxResults to apply per search term/location pair (matches INPUT_SCHEMA and README); global dedup unchanged.
- Prevent concurrent workers from exceeding per-search
maxResults via slot reservation (run-scheduling.ts).
- Parallelize business detail page + review GraphQL fetches when both are needed (lower wall-clock on review cohorts).
- Parallelize independent search term×location pipelines (up to 3 concurrent) for multi-city inputs.