Skip to content

Expose AbortSignal of the request timeout via context #3975

Description

@janbuchar

Why

tryCancel() is a synchronous signal.aborted read — it cannot interrupt a pending await. So on a handler timeout, await fetch(url) (or sendRequest, or a DB call) runs to completion and is then discarded at the next tryCancel(). The signal is the only way to actually abort in-flight foreign work.

The plumbing to consume it already exists end to end:

  • SendRequestOptions.signalsend-request.ts:38AbortSignal.any merge at base-http-client.ts:121fetch(…, { signal }) at :181impit.fetch(request, { signal }) in impit-client:105
  • HttpCrawler already relies on it for its own navigation: storage.getStore()?.cancelTask.signalhttp-crawler.ts:882
  • @apify/timeout aborts before it rejects (fire = () => { settled = true; context.cancelTask.abort(); reject(error) }), so the in-flight request rejects, that unwinds the handler, and the already-settled outer promise still reports our TimeoutError

What's missing is only that this is unreachable from supported API. Today it costs an undocumented import and an AsyncLocalStorage poke:

import { storage } from '@apify/timeout';

requestHandler: async ({ sendRequest }) => {
    const signal = storage.getStore()?.cancelTask.signal;
    await sendRequest({ url: '…' }, { signal });
}

We also already ship the other half of this surface publicly — context.extendTimeout(secs) (crawler_commons.ts:253) pushes the deadline, but nothing lets you observe it firing.

Ask

  • add context.signal: AbortSignal
  • pick its semantics first: context.cancelTask = parent?.cancelTask ?? new AbortController() means non-nested frames own separate controllers, so navigation (http-crawler.ts:462,522,579) and the handler (basic-crawler.ts:2100) have different ones, while raceWithTimeout — the whole-request timeout — has none at all, deliberately (request-timeout.ts:46-48). Inside the handler the answer is unambiguous and useful; elsewhere a naive getter returns a different object per phase, or undefined.

Non-goals

  • replacing tryCancel(). It stays the right tool for our own chokepoints (mostly checking on storage access)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    t-toolingIssues with this label are in the ownership of the tooling team.

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions