Repository navigation
Conversation
A handler can return Err(SuspensionRequest) to park the current rewritable unit on the heap and exit write()/end() with the non-poisoning RewritingError::Suspended; HtmlRewriter::resume() continues from a StateMachineBookmark. - take_owned() on each rewritable unit deep-copies borrowed byte slices so the unit outlives the write() stack frame - Arena::shift advances a start offset instead of memmoving the tail (a suspended rewrite re-feeds its unconsumed tail on every resume) - the pending captured-text flush is hoisted from Dispatcher::handle_tag into the lexer actions so every suspension point has a uniform shape - end_mut() is the non-consuming end() for a rewriter that may suspend in a document-end handler Bun uses this so an async JS content handler can suspend the rewrite and resume it from a promise reaction instead of spinning a nested event loop inside write(). See oven-sh/bun#36733.
The upstream ci.yml triggers on push: branches: [master] only, so an additive bun.yml runs cargo test --features=integration_test on pushes/PRs to bun without touching ci.yml (keeps rebases onto upstream clean).
…epth
The selector-VM open-element stack had two per-token scans that go
quadratic (or worse) on pathological nesting:
* Stack::pop_up_to did a full rposition() over the open stack for
every end tag. A stray end tag that matches nothing still walked
the whole stack, so N unclosed opens plus N stray closes was O(N^2):
"<a></b>".repeat(100_000) (~700 KB) burned ~25s of CPU.
* try_exec_hereditary_jumps_without_attrs walked every ancestor and
re-executed its hereditary_jumps for every start tag. For a selector
like "div div" over N nested <div>s every item carries the same
address range, so the walk is O(depth) per tag and O(N^2) overall;
k-arm chains ("div div div") compound to O(N^k).
Both paths are reachable from HtmlRewriter with any element-handler
registration, so untrusted HTML can pin a CPU core via sub-MB input.
Memory stays linear so max_allowed_memory_usage does not bound it.
Changes:
* Stack now keeps open_name_counts: HashMap<LocalName, usize> so
pop_up_to rejects a name not on the stack in O(1). When the name is
present the rposition scan length equals the subsequent drain
length, so total scan work over a document is bounded by total
pushes.
* Stack now keeps active_hereditary_jumps: Vec<(AddressRange, usize)>,
the distinct hereditary-jump ranges currently contributed by any
open item, each paired with the shallowest depth that introduced it.
Descendant matching iterates this flat list (bounded by program
size, not depth) and pop_up_to prunes it by depth. The per-item
has_ancestor_with_hereditary_jumps flag and the
HereditaryJumpPtr.stack_offset resume coordinate are removed.
* LocalName gets a manual Hash impl that ASCII-lowercases the Bytes
arm so it agrees with the existing case-insensitive PartialEq.
Without this, keying a HashMap on LocalName is unsound for any name
that falls off LocalNameHash (custom elements, >12 chars, chars
outside a-zA-Z1-6). This also fixes the same latent contract
violation in TypedChildCounterMap.
Re-executing the same address range against the same start tag was
already a no-op after the matched-id set union in add_execution_branch,
so dedup-once execution is observationally equivalent.
Collaborator
Author
|
Status
|
The oven-sh organization only allows actions that are pinned to a full-length commit SHA. The workflows use tag refs, so every job on this repository fails at "Set up job" in about 2 seconds: The actions actions/checkout@v4, dtolnay/rust-toolchain@stable, and actions/cache@v4 are not allowed in oven-sh/lol-html because all actions must be pinned to a full-length commit SHA. Pin each `uses:` in bun.yml, ci.yml and semgrep.yml to the commit that its tag points to today. The versions do not change. dtolnay/rust-toolchain is the exception. Its README requires a pinned commit to be in the history of `master`, because other commits get garbage-collected. The `stable` rev is one commit off `master` that only makes `stable` the default of the `toolchain` input. So pin the `v1` tag, which is on `master`, and pass `toolchain: stable`. Apart from that default, action.yml at `v1` is identical to action.yml at `stable`. Co-authored-by: SUZUKI Sosuke <sosuke@bun.com>
With the workflows actually executing again, -Dwarnings turns up three pre-existing issues on the bun branch: rustfmt drift in selectors_vm/mod.rs, an unused `Cell` import in the suspension tests, and an elided lifetime in the `text!` macro's type hint (elided_lifetimes_in_paths). No behaviour change. (cherry picked from commit eeea10d)
…d bound The suspension change made UserData::set_user_data require `Send` data; the C API still passed the raw `*mut c_void`, so lol_html_c_api no longer compiled (hidden while CI could not start). Store it in a `UserDataPtr` newtype that is `Send` -- the C API only stores and returns the pointer, and thread-safety of what it points to has always been the C caller's contract. c-api clippy and the c-tests suite pass again. (cherry picked from commit 4afc2ca)
cargo-semver-checks compares against the published lol_html of the same version. The bun branch intentionally adds API on top of 2.7.2 (RewritingError::Suspended from the suspension work) and is not published, so on this fork the check can only fail; keep it for cloudflare/lol-html. (cherry picked from commit 021637b, without the mention of lol_html::transform, which is not on bun yet)
robobun
force-pushed
the
robobun/6d0316ee/fix-upstream-ci-failures
branch
from
September 13, 2026 18:28
1194bc0 to
baeee5f
Compare
Collaborator
Author
|
The defects this PR fixes are still on
With the first two commits, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #7, which is the first commit here. Merge #7 first.
Problem
ci.ymlstart and fail on defects that are already onbun(run 34774752554).Testfails inRun Rust testsunderRUSTFLAGS=-Dwarnings:error: unused import: Cellanderror: hidden lifetime parameters in types are deprecated.Clippy and formatfails inCheck format:Diff in src/selectors_vm/mod.rs:508.c-apidoes not compile, becauseset_user_dataneedsSendsince 0f44e89.Check semverreportsenum_variant_addedforRewritingError::Suspended.Fix
TextChunk<'_>in the type hint oftext!, and applies rustfmt. The behavior does not change.c-apistore the user-data pointer in aUserDataPtrnewtype that isSend. 021637b runsCheck semveronly incloudflare/lol-html.Test,Clippy and format,cargo testandsemgrep/ciall pass.Check semvershows as skipped. The same steps pass locally (Notes).Background
ci.ymlcomes from upstream and runs on every pull request here, with warnings as errors. It also builds thec-apiandjs-apicrates. oven-sh/bun builds onlylol_html.Check semvercompares the public API with the crates.io release of the same version (2.7.2). The fork addsRewritingError::Suspendedto a public enum on purpose and is never published. So the check can only fail here.Notes
Commits
src/rewriter/mod.rs(test import),src/rewriter/settings.rs(text!),src/selectors_vm/mod.rs(rustfmt)c-api/src/*.rs.github/workflows/ci.ymlThe first two cherry-picks are verbatim. 021637b conflicted with the
# v2.9comment from #7 on the adjacentuses:line. The resolution keeps theif:and the comment of 021637b, without its mention oflol_html::transform, which is not onbunbefore #3 lands.Where the defects come from
Cellimport, theSendbound thatc-apidoes not meet, and the newRewritingError::Suspendedvariant.text!is upstream code (still on upstreammain). 0f44e89 exposed the lint, because its tests are the first to usetext!inside the crate.Merge and rebase
Co-authored-byonly.Earlier evidence
On the branch of #3 the same four commits made all three workflows green: run 34296114458 (CI), 34296114509 (bun), 34296114485 (Semgrep config).
Local run
Toolchain: nightly-2026-07-20 (CI uses stable 1.98.1). All with
RUSTFLAGS=-Dwarnings CARGO_INCREMENTAL=0 CARGO_PROFILE_DEV_DEBUG=0: