Failed network fetches never call back, leaving critical resources pending forever
- Dominant language
- Rust
- Stars
- 4.1k
- Forks
- 203
- Avg merge
- 8h 58m
- Merged PRs (30d)
- 112
Description
## Summary
When a network fetch fails (HTTP error status, IO error, or abort), the `NetHandler` for the request is silently dropped without any callback. If the request was a render-blocking ("critical") resource such as a `` in ``, its request id is never removed from `BaseDocument::pending_critical_resources`, so `has_pending_critical_resources()` returns `true` forever and painting is permanently blocked (`paint_scene` early-returns, and `blitz-shell` skips rendering).
## Details
- `NetHandler` only has a success path:
```rust
pub trait NetHandler: Send + Sync + 'static {
fn bytes(self: Box, resolved_url: String, bytes: Bytes);
}
```
(`packages/blitz-traits/src/net.rs`)
- `blitz-net`'s `Provider::fetch` logs errors and drops the handler without invoking anything:
```rust
match result {
Ok((response_url, bytes)) => { handler.bytes(response_url, bytes); ... }
Err(e) => {
tracing::error!(url = url.as_str(), error = ?e, "Error fetching");
}
};
```
(`packages/blitz-net/src/lib.rs`)
- The unblocking path is `Document::load_resource`, which removes the request id from `pending_critical_resources` *before* checking success/failure — so failures that are delivered as `Err` through `ResourceHandler::respond` (e.g. invalid UTF-8 CSS) unblock correctly. Only failures at the net-provider level never reach it.
Browsers treat a failed render-blocking stylesheet as "loaded with zero rules" and unblock rendering.
## Suggested fix
1. Give `NetHandler` an explicit failure path (e.g. `fn error(self: Box, err: String)` with a default no-op, or change `bytes` to take a `Result`), and make `blitz-net` (and other providers) deliver failures.
2. Belt-and-braces: a `Drop` impl on `ResourceHandler` that sends an `Err` `ResourceLoadResponse` if the handler was dropped without ever responding — this also covers third-party `NetProvider` implementations that drop handlers without calling anything (including the abort path).
3. Optionally, a timeout on render-blocking so a hung (never-completing) fetch cannot blank the page forever.
Related: this failure mode becomes more impactful with the proposed fix for #689 (deferring *style resolution*, not just painting, while critical resources are pending).
Contributor guide
Research direction
Start by reading the NetHandler trait in packages/blitz-traits/src/net.rs and the error branch of Provider::fetch in packages/blitz-net/src/lib.rs. Trace how Document::load_resource and ResourceHandler::respond clear pending_critical_resources on failures. Done means network-level failures, including dropped or aborted handlers, reach the failure path so render-blocking resources no longer leave painting blocked indefinitely.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100