0.1.3: deno_core 0.404 -> 0.408 (#37) - #38
Merged
Merged
Conversation
Version bump across Cargo.toml, browser_oxide_py and pyproject.toml (all three are checked for consistency by the verify job), plus a CHANGELOG section tracking the deferred deno_core bump. Scope for this release is #37: deno_core 0.404 -> 0.408, which aborts during V8 isolate construction in Linux debug builds. Signed-off-by: Yury Fedoseev <yfedoseev@gmail.com>
Re-applies the bump that was reverted from 0.1.2, plus the CI change needed to actually diagnose it. The blocker in #35 was not the abort itself but that its reason was invisible. libtest captures each test's stdout/stderr and replays it only on failure, so when a test *aborts* the process the captured output dies with it -- the log showed a bare `signal: 6, SIGABRT` against `workers::tests::create_worker` and `basic_js_execution`, with no message. Adding `--nocapture` streams output unbuffered, so the last thing printed before an abort is the abort's own diagnostic (a V8 FATAL line, or a panic that could not unwind out of a V8 callback). Ruled out so far: * Not a stack overflow. Rust detects those via its SIGSEGV handler and then calls abort(), so SIGABRT does not exclude it -- but the CI log contains no "has overflowed its stack", and `v8_recursion::test_thread_stack_is_at_ least_16mb` passes in the same debug run. `.cargo/config.toml` already sets RUST_MIN_STACK=67108864. * Not a new deno_core assertion: debug_assert counts in runtime/jsruntime.rs, runtime/mod.rs, runtime/bindings.rs and runtime/jsrealm.rs are unchanged between 0.404 and 0.408. * Not release-mode-reachable: on 0.408 in release the full suite passes, all 11 warm-reuse tests pass, the leak A/B is unchanged (1,040,662 -> 468 B per reuse) and examples/canvas_fp_probe.rs still reports len=17502 fnv1a=5b1d42ee9bdc9713. Transitive V8 moves 149.2.0 -> 149.4.0 with this bump, which is the other candidate and is not something deno_core's own source diff would show. The `--nocapture` change is worth keeping regardless of #37: any future abort-on-construction is otherwise undiagnosable from CI logs alone. Signed-off-by: Yury Fedoseev <yfedoseev@gmail.com>
Root cause of the 0.408 SIGABRT, found by reading the abort message that
`--nocapture` finally let through:
V8 posted a delayed task, but this isolate was created outside of a tokio
runtime context and the delay cannot be honored. Enter a tokio runtime
(e.g. via `tokio::runtime::Runtime::enter()`) before creating the `JsRuntime`.
deno_core 0.408 captures `tokio::runtime::Handle::try_current()` when it
registers an isolate (runtime/jsruntime.rs) and spawns V8's *delayed* foreground
tasks -- GC memory-reducer work and friends -- on that handle. With no handle,
runtime/setup.rs::spawn_delayed_task prints that line and calls
std::process::abort(). Upstream aborts rather than panics on purpose: V8 calls
it from C++ frames Rust cannot unwind through, so a panic message would be
swallowed anyway.
Two things I had wrong earlier, both now disproven:
* It is NOT debug-only. The abort is unconditional. Release builds passed only
while V8 happened not to post a delayed task inside the window under test.
Release was exposed too.
* It is NOT Linux-only. Reproduced on Windows in a standalone crate; CI shows
ubuntu, macos AND windows failing on #38.
Proven with a minimal deno_core-only crate (no skia, so debug links on Windows),
constructing an isolate and churning the heap to provoke GC scheduling:
no tokio entered handle_at_ctor=false -> abort 0xC0000409
runtime entered handle_at_ctor=true -> clean exit 0
Fix: `ensure_tokio_context()` enters a process-lifetime fallback runtime for the
duration of construction when the caller has none, applied to both the page and
worker realms. The fallback must outlive the isolate -- the captured handle would
dangle otherwise -- hence a OnceLock rather than a temporary. Keeping this in the
library rather than pushing it onto callers is deliberate:
BrowserJsRuntime::new/with_profile/with_options are synchronous public API,
callable from a plain fn main or a #[test], and the failure mode is a process
abort, not an error.
Also, per review: V8 heap limits are now environment-tunable instead of
hard-coded, since the right ceiling is a property of the deployment and the
fixed 4 GB silently over-committed small containers.
BROWSER_OXIDE_HEAP_MAX_MB default 4096 (4 GB)
BROWSER_OXIDE_HEAP_INITIAL_MB default 1024 (1 GB)
Unparseable/zero values fall back to defaults with a warning rather than
failing; an initial above the ceiling is clamped, because V8 rejects that
pairing. Documented in docs/CONFIGURATION.md, including that the limits are
per-isolate so a PagePool of N commits up to N x the ceiling, plus a note on the
new tokio-context requirement for embedders.
tests/runtime_env.rs covers both. It is one #[test] fn on purpose: each check
needs its own isolate, V8 requires reverse-order drops, and splitting them
across fns aborted the binary. It must stay a plain #[test] -- a #[tokio::test]
would enter a runtime and silently stop testing the regression.
Signed-off-by: Yury Fedoseev <yfedoseev@gmail.com>
yfedoseev
marked this pull request as ready for review
July 28, 2026 01:15
Replaces the placeholder Planned entry with what actually shipped: the #37 tokio-context fix, env-tunable heap limits, deno_core 0.408, the --nocapture CI change, and the verification results (canvas fingerprint unchanged across the V8 bump, zero real-site regressions, full matrix green). Signed-off-by: Yury Fedoseev <yfedoseev@gmail.com>
…bsence
`Release MCP binary` failed for v0.1.2 at "Generate release notes from CHANGELOG":
No '> subtitle' blockquote found under '## [0.1.2]' in CHANGELOG.md.
`extract-release-notes.sh` requires a `> ...` blockquote immediately under the
version header (it becomes the GitHub Release title) plus at least one `### `
heading. The 0.1.2 and 0.1.3 sections had neither subtitle. Added both, verified
with the script directly.
The more important fix is the gate. Changelog Gate only grepped for the section
header:
grep -qE "^##+ \[?${V//./\\.}\]?" CHANGELOG.md
so it went green while the release was guaranteed to fail. v0.1.2 passed the
gate, was tagged, published to crates.io, and only then did the MCP release die
on a missing subtitle. A post-tag failure is the expensive kind: the tag and the
crates.io publish are already public and cannot be withdrawn, only superseded.
The gate now runs the same validator the release workflows run
(`extract-release-notes.sh --check`) instead of a parallel, weaker check. One
validator, one source of truth — a header-only check could always pass while the
real extraction failed.
Note v0.1.2's GitHub Release is still absent. Its four binaries built fine; only
the release-creation step failed, and re-running cannot help because the v0.1.2
tag points at a commit without the subtitle. It needs `gh release create v0.1.2`
by hand if we want it, which is a separate decision from unblocking 0.1.3.
Signed-off-by: Yury Fedoseev <yfedoseev@gmail.com>
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.
Draft. Scope for 0.1.3 is #37 — the
deno_core0.408 bump deferred from #35.Why this is a draft
0.408 is expected to fail the ubuntu debug test jobs with
signal: 6, SIGABRTduring V8 isolate construction. That is the known symptom from #37, reproduced deliberately. This PR exists to get the reason, which was invisible last time.The actual change of substance
cargo testnow runs with--nocapture. That is not cosmetic: libtest captures each tests stdout/stderr and replays it only on failure, so when a test **aborts the process** the captured output dies with it. In #35 that left a baresignal: 6againstworkers::tests::create_workerandbasic_js_executionwith no diagnosis, which is why the bump was reverted rather than fixed. Unbuffered output means the last line before an abort is the aborts own message — a V8FATAL, or a panic that could not unwind out of a V8 callback.Worth keeping regardless of the outcome here: any future abort-on-construction is otherwise undiagnosable from CI logs alone.
Ruled out so far
abort(), so SIGABRT does not exclude it — but the CI log contains nohas overflowed its stack, andv8_recursion::test_thread_stack_is_at_least_16mbpasses in the same debug run..cargo/config.tomlalready setsRUST_MIN_STACK=67108864.debug_assertcounts inruntime/jsruntime.rs,runtime/mod.rs,runtime/bindings.rsandruntime/jsrealm.rsare identical between 0.404 and 0.408.examples/canvas_fp_probe.rsstill reportslen=17502 fnv1a=5b1d42ee9bdc9713.Transitive V8 moves 149.2.0 -> 149.4.0 with this bump — the other candidate, and one deno_core`s own source diff cannot show.
Next
Do not merge while the debug jobs are red.