BabylonJS / BabylonJS/JsRuntimeHost

fetch/XHR transport failures and unhandled rejections lose all diagnostic fidelity before reaching the embedding app

Open
#196 0 comments 0 reactions 1 assignee Claimed by @bkaradzic-microsoft View on GitHub
Dominant language
C++
Stars
22
Forks
23
Avg merge
3d 14h
Merged PRs (30d)
6

Description

Applications embedding Babylon Native depend on field crash reports for ongoing maintenance, and today a `fetch()`/`XMLHttpRequest` failure arrives there with no usable signal: DNS failure, connection refused, TLS rejection, proxy auth failure, server outage, and a missing bundled `app:///` asset are all byte-identical, and an un-caught rejection never reaches the host (BN) application at all. This issue tracks the chain of fixes; each hop is independently shippable.

### The chain (where fidelity is lost today)

1. ~~**UrlLib swallows transport errors at the source.**~~ ✅ **Fixed by BabylonJS/UrlLib#31** (open): the Apple backend discarded the `NSError` (long-standing in-code TODO) and the curl backend caught and dropped its own failure, leaving only `StatusCode() == 0`. UrlLib now exposes `ErrorString()` / `ErrorSymbol()` / `ErrorCode()`, normalized as `":(): "` (e.g. `curl:CURLE_COULDNT_RESOLVE_HOST(6): ...`, `nsurl:NSURLErrorCannotConnectToHost(-1004): ...`, `urllib:AppResourceNotFound(0): ...`) — stable tokens for observability-pipeline filtering, purely additive (status-0 contract unchanged).

2. **The fetch polyfill flattens every transport failure to a constant string.** [`Fetch.cpp`](https://github.com/BabylonJS/JsRuntimeHost/blob/main/Polyfills/Fetch/Source/Fetch.cpp#L346-L353) throws `std::runtime_error{"fetch: network request failed"}`, discarding `result.error()` and the URL/method context it has in scope; the rejection surfaces as a plain `Error` (browsers/Node/Bun: `TypeError`) with no `cause`, no `code`, and a `.stack` snapshotted inside the scheduler tick — i.e. zero user frames. Proposed shape, once the UrlLib pin includes #31:
- reject with a **`TypeError`** whose message is **stable** (`"fetch failed"` style — keeps crash-report grouping intact), carrying the variable detail as properties: `cause` (message from `ErrorString()`), `code` (`ErrorSymbol()`), `url`;
- capture the JS call-site stack **synchronously inside `fetch()`** before `SendAsync()` (create the rejection Error, or a stack carrier, while user frames are still on the stack — the undici approach) so crash reports can attribute the failing call;
- same treatment for `XMLHttpRequest`'s error path.

3. **Unhandled promise rejections never reach `UnhandledExceptionHandler`.** [`AppRuntime::Dispatch`](https://github.com/BabylonJS/JsRuntimeHost/blob/main/Core/AppRuntime/Source/AppRuntime.cpp#L108-L122) only catches synchronous `Napi::Error` throws from dispatched callbacks; no engine-level rejection tracker is wired anywhere in Core, so a fire-and-forget `fetch()` failure (or a throw inside any `.then`) vanishes silently — the embedder's handler never fires and the process exits 0. Proposal: an opt-in `AppRuntime::Options` handler (or routing into the existing one) fed per engine — `Isolate::SetPromiseRejectCallback` (V8), `JsSetHostPromiseRejectionTracker` (Chakra), `JSGlobalContextSetUnhandledRejectionCallback` (JavaScriptCore), and the JSI tracker — the per-engine seams already exist as `AppRuntime_{V8,Chakra,JavaScriptCore,JSI}.cpp`.

4. **`fetch` ignores `init.signal`.** The implementation passes `arcana::cancellation::none()` for both continuations, so abort never rejects (`AbortError`) and never cancels the transport. Two prerequisites: `UrlRequest::Abort()` currently only cancels the Windows backend (the curl/NSURLSession backends never observe `m_cancellationSource`), and the `AbortSignal` polyfill predates the modern spec (no `reason`/`throwIfAborted()`, writable `aborted`). Worth sequencing with BabylonJS/BabylonNative#1708, which installs `AbortController` globally in the Playground — after which library feature-detection passes while signals silently no-op, and user-initiated cancellations become indistinguishable from network failures in telemetry.

### Validation

Beyond unit tests per hop (UrlLib#31 already ships offline-deterministic transport-failure tests + CI), the highest-value conformance ports for this area: the WPT `fetch/api/abort/general.any.js` core cases (pre-aborted/mid-flight/post-settle, `signal.reason`), undici's `fetch failed`-with-`cause` assertions from `test/fetch/client-fetch.js`, a four-way failure-shape bank (refused / NXDOMAIN / bad TLS / missing local asset must be distinguishable and `TypeError`-shaped), and a native-side test that an unhandled rejection actually reaches the host handler.

Happy to submit PRs for hops 2–4 individually (2 is unblocked as soon as the UrlLib pin can include #31; 3 and 4 are independent).

Related: #188 (fetch polyfill), #195 (statusText — same UrlLib-consumption pattern hop 2 would follow), BabylonJS/BabylonNative#1707.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.