fix: prevent JS stack overflow from crashing the process#15
Conversation
…hing QuickJS's stack overflow check is a soft limit measured against the native thread stack. If max_stack_size reaches the thread stack, JS overflows the real stack first and the process aborts (SIGABRT) instead of throwing a catchable RangeError. JS runs on flutter_rust_bridge's tokio workers (2 MB stack, which fjs does not set), and `0` means "no limit" to QuickJS — both overflow the native stack. Clamp the requested budget to 3/4 of the thread stack, mapping 0 to that ceiling, so overflow stays a catchable RangeError. Tests cover that eval and pumped jobs reach the same depth, a bigger budget reaches deeper, and an over-large budget no longer crashes a worker thread. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
flutter_rust_bridge runs Rust on tokio workers whose stack defaults to 2 MB, which fjs cannot resize. That is far below a browser's ~8 MB JS stack, so deep-but-normal JS (e.g. a recursive UI render) overflows the native stack and aborts the process instead of throwing. Run every JS entry (eval, call, module eval, job pump) on fjs's own runtime whose threads have an 8 MB stack, via a small `with_js` helper and a global `js_executor`. QuickJS refreshes its overflow baseline on each entry, so spreading JS across these threads stays correct. Default the budget to 6 MB (3/4 of the thread stack) and keep the clamp relative to it, so deep UIs render while overflow stays a catchable RangeError. The sync runtime, which runs on the caller's thread, keeps its conservative ceiling. No API changes and no new bridged methods; `execute_pending_job`'s signature is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Review findings:
Verification run on this PR: |
Problem
QuickJS's stack overflow check is a soft limit: it only fires once JS grows past
max_stack_size, and it does not know the real native thread stack. JS runs on flutter_rust_bridge's tokio workers, whose stack defaults to 2 MB. So:max_stack_sizeis set near or above the thread stack (or0, which QuickJS treats as "no limit"), JS overflows the native stack first and the whole process aborts (SIGABRT) instead of throwing a catchableRangeError.Fix
max_stack_sizebelow the thread stack, mapping0to that ceiling, so overflow is always a catchableRangeError, never a crash.No public API changes, no new bridged methods, no codegen.
execute_pending_job's signature is unchanged.Tests
Host-side tests (no device): eval and pumped jobs reach the same depth and both throw a catchable error; a bigger budget reaches deeper on both paths; the default budget is generous; an over-large budget is clamped instead of crashing a worker thread.
🤖 Generated with Claude Code