Skip to content

fix(async): linearize await inside catch; complete the RegExp test262 worklist - #8716

Merged
proggeramlug merged 1 commit into
mainfrom
merge/b20
Aug 24, 2026
Merged

fix(async): linearize await inside catch; complete the RegExp test262 worklist#8716
proggeramlug merged 1 commit into
mainfrom
merge/b20

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Lands #8707 and #8667.

#8707await inside catch compiled to a blocking busy-wait (fixes #8681)

build_async_catch_route_body_direct ignored catch_entry_state and re-inlined the catch body through rewrite_yield_to_await_in_stmts, turning every catch suspend back into a raw Expr::Await that codegen lowers as js_wait_for_event. Reached re-entrantly from the async-step cascade — a rejected awaited promise in a try routing to a catch that itself awaits, i.e. the ordinary stream retry/backoff shape — that blocking wait monopolises the single runtime thread while the driver that would resolve it sits suspended above it on the stack. The program deadlocks. This is the natively-compiled Claude Code -p streaming hang.

The revision also closes the await-in-finally gap that its own test caught. When I first validated this against main, async_generator_linearizes_every_await_position reported await-in-finally: 2 raw await(s) survived; it passed on the PR's own head, so it was an interaction with newer main, not a defect in the PR as written. The fix is that a linearized catch now takes the state-based upper bound (protected_end_state, which excludes the catch's own states), so an error raised inside the catch routes correctly. The legacy inline path (catch_entry_state == None) keeps the bound it always used. That test now passes on the merged result.

#8667 — complete the RegExp test262 worklist

Now rebases cleanly. I checked the Cargo.toml conflict I'd flagged as risky to union-resolve: the change is purely additive — regress gains features = ["utf16"], nothing dropped — and cargo check --locked still succeeds, so the lockfile is current and CI's locked build won't break.

One fix on top

#8707's new coverage pushed async_to_generator.rs to 2722 lines, past the file-size gate. computed_and_field_async_tests moved to a sibling file declared with #[path = "async_to_generator_tests.rs"], so the module path — and therefore every test name — is unchanged. Verified by running them: they still report as async_to_generator::computed_and_field_async_tests::*, 8/8. 2722 → 1806.

Validation (on the merged result)

  • All 30 lint-job checkers pass
  • perry-transform --lib: 92 passed, 0 failed (count unchanged across the split, so no test was lost)
  • perry-runtime --lib (RUST_TEST_THREADS=1): 2657 passed, 0 failed (+2 from fix(runtime): complete RegExp test262 worklist #8667)
  • perry-codegen --lib: 1214 passed, 0 failed
  • cargo check --locked -p perry-runtime: clean
  • Squashed tree verified identical to the validated tree

No version bump.

Summary by CodeRabbit

  • Bug Fixes

    • Improved regular expression handling for quantified captures, nullable repetitions, and captures in lookarounds.
    • Preserved lone surrogate characters during regular expression replacement.
    • Fixed hangs caused by await inside catch blocks in async functions and generators.
    • Improved async-generator support for class methods, closures, and computed members.
  • Tests

    • Added regression coverage for regular expression capture and replacement behavior.
    • Expanded async transformation coverage across supported syntax patterns.

… worklist

Lands #8707 and #8667.

#8707 (fixes #8681) stops an `await` inside a `catch` compiling to a
blocking busy-wait. `build_async_catch_route_body_direct` ignored
`catch_entry_state` and re-inlined the catch body through
`rewrite_yield_to_await_in_stmts`, turning every catch suspend back into a
raw `Expr::Await` that codegen lowers as `js_wait_for_event`. Reached
re-entrantly from the async-step cascade -- a rejected awaited promise in
a `try` routing to a `catch` that itself awaits, the ordinary stream
retry/backoff shape -- that blocking wait monopolises the single runtime
thread while the driver that would resolve it sits suspended above it, and
the program deadlocks. This is the natively-compiled Claude Code `-p`
streaming hang.

The revision also closes the `await`-in-`finally` gap its own test caught:
a linearized catch now takes the state-based upper bound
(`protected_end_state`, which excludes the catch's own states), so an
error raised inside the catch routes correctly. The legacy inline path
(`catch_entry_state == None`) keeps the bound it always used.

#8667 completes the RegExp test262 worklist. Its `Cargo.toml` change is
additive -- `regress` gains `features = ["utf16"]`, nothing dropped -- and
`cargo check --locked` still succeeds, so the lockfile is current.

One fix on top: #8707's new coverage pushed `async_to_generator.rs` to
2722 lines, past the file-size gate. `computed_and_field_async_tests` moved
to a sibling file declared with `#[path]`, so the module path and every
test name are unchanged (verified by running them). 2722 -> 1806.

No version bump.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a11d4d2-9b68-4df8-856a-160ec0f19b3d

📥 Commits

Reviewing files that changed from the base of the PR and between 60f4fab and c1a77e5.

📒 Files selected for processing (10)
  • changelog.d/8667-regexp-repeat-matcher.md
  • changelog.d/8707-await-in-catch-blockwait.md
  • crates/perry-runtime/Cargo.toml
  • crates/perry-runtime/src/regex.rs
  • crates/perry-runtime/src/regex/repeat_matcher.rs
  • crates/perry-runtime/src/regex/tests.rs
  • crates/perry-transform/src/async_to_generator.rs
  • crates/perry-transform/src/async_to_generator_tests.rs
  • crates/perry-transform/src/generator/lower/async_step.rs
  • crates/perry-transform/src/generator/mod.rs

📝 Walkthrough

Walkthrough

The PR updates regex matcher selection and WTF-8 replacement handling. It also records async generator methods and routes errors through linearized async catch states. Regression tests cover both runtime and transformation changes.

Changes

Regex matching and replacement

Layer / File(s) Summary
Regex matcher selection
crates/perry-runtime/src/regex/repeat_matcher.rs, crates/perry-runtime/src/regex/tests.rs
Negative lookaround captures now select the ECMAScript backtracking matcher. Tests cover negative lookahead capture behavior.
WTF-8 replacement flow
crates/perry-runtime/Cargo.toml, crates/perry-runtime/src/regex.rs, crates/perry-runtime/src/regex/repeat_matcher.rs, crates/perry-runtime/src/regex/tests.rs, changelog.d/8667-regexp-repeat-matcher.md
Regex replacement preserves lone surrogates through UTF-16 and WTF-8 conversion. Replacement expansion supports captures and named groups.

Async generator transformation

Layer / File(s) Summary
Async generator registration and transformation tests
crates/perry-transform/src/generator/mod.rs, crates/perry-transform/src/async_to_generator.rs, crates/perry-transform/src/async_to_generator_tests.rs
Async generator methods and computed members record function IDs. Expanded tests validate await collection, linearization, closure rewriting, and nested async generators.
Linearized async catch dispatch
crates/perry-transform/src/generator/lower/async_step.rs, changelog.d/8707-await-in-catch-blockwait.md
Linearized catches dispatch delivered errors to catch states. Throws raised inside catch bodies escape to enclosing handlers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant js_string_replace_regex
  participant replace_wtf8_subject
  participant regress
  js_string_replace_regex->>replace_wtf8_subject: pass WTF-8 subject and replacement
  replace_wtf8_subject->>regress: match UTF-16 or UCS-2 units
  regress-->>replace_wtf8_subject: return match captures
  replace_wtf8_subject-->>js_string_replace_regex: return WTF-8 replacement output
Loading
sequenceDiagram
  participant async_step
  participant catch_dispatch
  participant async_step_loop
  async_step->>catch_dispatch: deliver error to linearized catch
  catch_dispatch->>async_step_loop: set catch_entry_state
  async_step_loop->>catch_dispatch: execute catch states
  catch_dispatch-->>async_step: propagate catch-raised throw
Loading

Suggested reviewers: thehypnoo

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch merge/b20

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant