Skip to main content

Module fetch_async

Module fetch_async 

Source
Expand description

Async fetch/HTTP integration with the event loop (FetchTasklet pattern).

§Architecture (FetchTasklet event-driven paradigm)

Every JS-native http/https/tls entry returns a pending Promise and delegates the actual network I/O to AsyncHTTP::init + HTTPThread::schedule. The HTTPThread runs a dedicated epoll loop and calls back on_http_done (pure-Rust, zero SM API) when the response is ready. That callback writes the result into the outcome slot and enqueues a ConcurrentTask (resolve_tasklet) on the JS thread’s MiniEventLoop, which wakes the JS thread via us_wakeup_loop. The JS-thread ConcurrentTask callback builds the Response/error JS object and ResolvePromise/RejectPromises.

This mirrors Bun’s FetchTasklet design exactly:

  • AsyncHTTP::init+schedule = Bun’s FetchTasklet::init+schedule
  • on_http_done = Bun’s HTTPCallback (HTTPThread, pure-Rust)
  • resolve_tasklet = Bun’s JS-thread resolve via ConcurrentTask
  • poll_ref::ref/unref_concurrently = Bun’s refConcurrently keepalive

§Why this replaced thread::spawn (BCE-20260619-010)

The prior thread::spawn + drain_pending polling model had three flaws:

  1. O(N) OS threads for N concurrent fetches (violates REQ-ENG-010: “N并发fetch占OS线程数=O(1)”)
  2. JS-thread busy-poll sleep(1ms) in the fetch-only case (wasteful)
  3. drain_pending must be called every tick (fragile coupling)

The event-driven model fixes all three: HTTPThread uses a single epoll fd, ConcurrentTask auto-wakes the JS thread, and no polling is needed.

§Scope

Shared helper used by the HTTP-sweep entries:

  • node_http.rs:http_request / http_get
  • node_https.rs:https_request
  • node_tls.rs:tls_connect

h3_fetch.rs is excluded — it has no send_sync path.

Structs§

AbortRequest
One abort wiring request handed to start_with_signal.
FetchTlsInit
Per-fetch TLS options parsed from WHATWG-fetch init.tls (the Node undici dispatcher tls option subset): custom trust anchors, explicit verification opt-out, SNI override. None end-to-end = the previous behaviour byte-for-byte (stealth profile only, system roots, verify on).
PendingFetch
A fetch tasklet: pending Promise + event-driven HTTP integration.

Enums§

ResolveKind
How to materialize the result as a JS object on resolve. Different JS-native entries want different shapes: fetch/http.request/https.request want a Response; tls.connect (a TLS handshake probe) wants a TLSSocket.

Functions§

has_pending
JS-thread poll: are there any outstanding async fetches on this thread?
new_abort_id
Allocate a fresh abort registry id (JS-thread, at fetch() call time).
reject_promise_with_abort_error
Reject a Promise with a DOMException AbortError — the WHATWG fetch signal-abort rejection value: name “AbortError”, message “The operation was aborted”. Constructs the realm’s real DOMException (globals.rs class or servo’s native one, whichever the realm carries) so instanceof DOMException holds; a plain name/message error object is used only when the realm genuinely has no DOMException constructor.
start
Schedule an async fetch via AsyncHTTP::init + HTTPThread::schedule. The caller must have already created the pending Promise via JS::NewPromiseObject(cx, null), pass it here as promise_val (an Object JSVal), and then set args.rval() to the same value before returning from the extern-C trampoline.
start_fetch
fetch()-native entry: WHATWG fetch(input, init) with both optional channels — init.signal (abort) and init.tls (undici dispatcher tls subset, see FetchTlsInit). start/start_with_signal are the Node-API entries (http/https/http2) and carry no per-fetch tls options.
start_tls_probe
Schedule a TLS handshake probe: a single stealth HTTPS HEAD against host:port. The Promise resolves to a TLSSocket-shaped object (authorized/encrypted/servername) on success, or rejects on error. host is captured so the resolver can surface it as servername.
start_with_signal
Signal-aware variant used by fetch(input, { signal }). Same contract as start; additionally wires the abort flag into the AsyncHTTP’s Signals.aborted so a later trigger_abort cancels the in-flight request and fails the task with Aborted.
trigger_abort
Fire the cancellation channel for the fetch registered under abort_id. Called on the JS thread when the AbortSignal’s abort event fires (and is idempotent: a second call finds the entry either still present — flag already true — or already removed by resolve_tasklet).