fix: use try_init() to avoid SetGlobalDefaultError panic#9078
fix: use try_init() to avoid SetGlobalDefaultError panic#9078fengmk2 wants to merge 1 commit intorolldown:mainfrom
Conversation
When rolldown is embedded in a host binary (e.g., vite-plus) that has already installed a global tracing subscriber, DebugTracer::init() and try_init_tracing() panic because .init() calls set_global_default() which fails if a subscriber is already set. Replace .init() with .try_init().ok() at all 4 call sites so a previously-installed subscriber is silently accepted instead of causing a panic. Ref: voidzero-dev/vite-plus#1356
✅ Deploy Preview for rolldown-rs canceled.
|
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR updates tracing initialization to avoid panicking when a global tracing subscriber has already been installed by an embedding host binary.
Changes:
- Replaces
tracing_subscriber::util::SubscriberInitExt::init()withtry_init()inrolldown_tracing::try_init_tracing(). - Replaces
init()withtry_init()inrolldown_devtools::DebugTracer::init(). - Silently ignores
SetGlobalDefaultErrorinstead of panicking during devtools/tracing setup.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
crates/rolldown_tracing/src/lib.rs |
Switches tracing subscriber setup to try_init() across output modes to avoid SetGlobalDefaultError panics. |
crates/rolldown_devtools/src/init_tracing.rs |
Switches devtools tracing subscriber setup to try_init() to avoid global subscriber re-install panics. |
| tracing_subscriber::registry() | ||
| .with(Targets::from_str(&env_var).unwrap()) | ||
| .with(chrome_layer.with_filter(filter_for_removing_devtools_event)) | ||
| .init(); | ||
| .try_init() | ||
| .ok(); |
There was a problem hiding this comment.
try_init().ok(); still discards a #[must_use] return value (both Result and the Option from .ok()), which will trigger unused_must_use warnings. Also, this branch returns Some(guard) even if try_init() fails (e.g., global subscriber already set), which can leave an unused guard/file handle around. Capture the Result from try_init() (e.g., let _ = ... or if ... .is_ok() { ... }) and only return the guard when initialization actually succeeded (or short-circuit before building the chrome layer when a global subscriber is already installed).
| tracing_subscriber::registry() | ||
| .with(filter_for_removing_devtools_event) | ||
| .with(Targets::from_str(&env_var).unwrap()) | ||
| .with( | ||
| fmt::layer().pretty().with_span_events(FmtSpan::NONE).with_level(true).with_target(false), | ||
| ) | ||
| .init(); | ||
| .try_init() | ||
| .ok(); | ||
| tracing::debug!("Tracing initialized"); |
There was a problem hiding this comment.
try_init().ok(); discards a #[must_use] value and is likely to produce an unused_must_use warning. Additionally, tracing::debug!("Tracing initialized") will run even if try_init() fails (e.g., subscriber already set), which makes the log message inaccurate; consider logging only on Ok(()) (or logging a different message on Err).
| tracing_subscriber::registry() | ||
| .with(filter_for_removing_devtools_event) | ||
| .with(Targets::from_str(&env_var).unwrap()) | ||
| .with(fmt::layer().pretty().with_span_events(FmtSpan::CLOSE | FmtSpan::ENTER)) | ||
| .init(); | ||
| .try_init() | ||
| .ok(); | ||
| tracing::debug!("Tracing initialized"); | ||
| None |
There was a problem hiding this comment.
Same issue as the other branch: try_init().ok(); still drops a #[must_use] value (warning), and the unconditional Tracing initialized debug log is misleading when try_init() returns Err because a global subscriber is already installed.
| tracing_subscriber::registry() | ||
| .with(DevtoolsLayer.with_filter(devtools_event_filter)) | ||
| .with(fmt::layer().event_format(DevtoolsFormatter)) | ||
| .init(); | ||
| .try_init() | ||
| .ok(); |
There was a problem hiding this comment.
try_init().ok(); still discards a #[must_use] return value (the Option from .ok()), so this will likely trigger an unused_must_use warning during cargo check. Prefer capturing the result explicitly (e.g., let _ = ...try_init();) or handling/logging the Err case.
|
refs #8698 (comment) |
|
I guess this would disable the debug file output is used by the |
|
@sapphi-red Indeed, we need to think of a better solution. Do you have any suggestions? |
|
I'm not familiar with the code around here and not sure how it's working in details. @hyf0 Do you have any ideas? |
|
For the future plan: rolldown/meta/design/devtools.md Line 263 in 0af9533 Should be able to solve it without causing conflict with other crates. |
…s is enabled (#1364) ## Summary Fixes a panic that occurs when `build.rolldownOptions.devtools` is set in `vite.config.ts` (commonly via `@vitejs/devtools`): ``` Rolldown panicked. This is a bug in Rolldown, not your code. thread '<unnamed>' panicked at tracing-subscriber-0.3.23/src/util.rs:94:14: failed to set global default subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set") ``` ## Root cause The vite-plus NAPI binary bundles `rolldown_binding` (including `rolldown_devtools`) into a single `.node` file. Two paths inside this same binary each try to install a global tracing subscriber: 1. **NAPI module load** → `vite_shared::init_tracing()` — previously installed a no-op subscriber (empty `Targets` filter) even when `VITE_LOG` was unset, occupying the global default slot. 2. **Build with devtools enabled** → `DebugTracer::init()` — tries to install its own subscriber and panics because the slot is already taken. ## Fix - Skip subscriber installation entirely when `VITE_LOG` is not set, leaving the global default slot free for `DebugTracer::init()` (the common case — no one pays for tracing they didn't ask for). - Use `.try_init().ok()` instead of `.init()` so a double-init still fails gracefully rather than panicking. ## Test plan - [x] Reproduced the panic at `/tmp/repro-1356` with `@vitejs/devtools@0.1.13` + `build.rolldownOptions.devtools: {}`. - [x] Rebuilt with `RELEASE_BUILD=1 pnpm bootstrap-cli`; `npx vp build` now completes successfully. - [x] `VITE_LOG=debug vp build` still prints traces (subscriber is installed when requested). ## Related Upstream rolldown also makes the same `.init()` → `.try_init()` change as defense-in-depth: rolldown/rolldown#9078. That upstream fix is not required for this patch — the vite-plus fix here is sufficient on its own. Closes #1356
Summary
.init()with.try_init().ok()inrolldown_devtools::DebugTracer::init()and all 3 call sites inrolldown_tracing::try_init_tracing()SetGlobalDefaultErrorpanic when rolldown is embedded in a host binary that has already installed a global tracing subscriberContext
When rolldown is bundled into a host binary (e.g., vite-plus bundles
rolldown_bindingviapub extern crate), the host may install a global tracing subscriber during NAPI module init. Later, whenDebugTracer::init()runs (triggered bybuild.rolldownOptions.devtools: {}), it calls.init()which panics:.try_init()returnsResultinstead of panicking, so a previously-installed subscriber is silently accepted.Ref: voidzero-dev/vite-plus#1356
Test plan
cargo check -p rolldown_devtools -p rolldown_tracingpasses