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’sFetchTasklet::init+scheduleon_http_done= Bun’sHTTPCallback(HTTPThread, pure-Rust)resolve_tasklet= Bun’s JS-thread resolve via ConcurrentTaskpoll_ref::ref/unref_concurrently= Bun’srefConcurrentlykeepalive
§Why this replaced thread::spawn (BCE-20260619-010)
The prior thread::spawn + drain_pending polling model had three flaws:
- O(N) OS threads for N concurrent fetches (violates REQ-ENG-010: “N并发fetch占OS线程数=O(1)”)
- JS-thread busy-poll
sleep(1ms)in the fetch-only case (wasteful) drain_pendingmust 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_getnode_https.rs:https_requestnode_tls.rs:tls_connect
h3_fetch.rs is excluded — it has no send_sync path.
Structs§
- Abort
Request - One abort wiring request handed to
start_with_signal. - Fetch
TlsInit - Per-fetch TLS options parsed from WHATWG-fetch
init.tls(the Node undicidispatchertls option subset): custom trust anchors, explicit verification opt-out, SNI override.Noneend-to-end = the previous behaviour byte-for-byte (stealth profile only, system roots, verify on). - Pending
Fetch - A fetch tasklet: pending Promise + event-driven HTTP integration.
Enums§
- Resolve
Kind - How to materialize the result as a JS object on resolve. Different
JS-native entries want different shapes:
fetch/http.request/https.requestwant aResponse;tls.connect(a TLS handshake probe) wants aTLSSocket.
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 realDOMException(globals.rs class or servo’s native one, whichever the realm carries) soinstanceof DOMExceptionholds; 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 viaJS::NewPromiseObject(cx, null), pass it here aspromise_val(an Object JSVal), and then setargs.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) andinit.tls(undici dispatcher tls subset, seeFetchTlsInit).start/start_with_signalare 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.hostis captured so the resolver can surface it asservername. - start_
with_ ⚠signal - Signal-aware variant used by
fetch(input, { signal }). Same contract asstart; additionally wires the abort flag into the AsyncHTTP’sSignals.abortedso a latertrigger_abortcancels the in-flight request and fails the task withAborted. - trigger_
abort - Fire the cancellation channel for the fetch registered under
abort_id. Called on the JS thread when the AbortSignal’sabortevent fires (and is idempotent: a second call finds the entry either still present — flag already true — or already removed byresolve_tasklet).