Skip to content

fix: use try_init() to avoid SetGlobalDefaultError panic#9078

Closed
fengmk2 wants to merge 1 commit intorolldown:mainfrom
fengmk2:fix/tracing-try-init
Closed

fix: use try_init() to avoid SetGlobalDefaultError panic#9078
fengmk2 wants to merge 1 commit intorolldown:mainfrom
fengmk2:fix/tracing-try-init

Conversation

@fengmk2
Copy link
Copy Markdown

@fengmk2 fengmk2 commented Apr 12, 2026

Summary

  • Replace .init() with .try_init().ok() in rolldown_devtools::DebugTracer::init() and all 3 call sites in rolldown_tracing::try_init_tracing()
  • Prevents SetGlobalDefaultError panic when rolldown is embedded in a host binary that has already installed a global tracing subscriber

Context

When rolldown is bundled into a host binary (e.g., vite-plus bundles rolldown_binding via pub extern crate), the host may install a global tracing subscriber during NAPI module init. Later, when DebugTracer::init() runs (triggered by build.rolldownOptions.devtools: {}), it calls .init() which panics:

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")

.try_init() returns Result instead 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_tracing passes
  • CI passes

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
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 12, 2026

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit ac096e6
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69dba1574495080008b8fdb1

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 12, 2026

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing fengmk2:fix/tracing-try-init (ac096e6) with main (b3843b3)

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() with try_init() in rolldown_tracing::try_init_tracing().
  • Replaces init() with try_init() in rolldown_devtools::DebugTracer::init().
  • Silently ignores SetGlobalDefaultError instead 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.

Comment on lines 51 to +55
tracing_subscriber::registry()
.with(Targets::from_str(&env_var).unwrap())
.with(chrome_layer.with_filter(filter_for_removing_devtools_event))
.init();
.try_init()
.ok();
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines 64 to 72
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");
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines 76 to 83
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
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 38 to +42
tracing_subscriber::registry()
.with(DevtoolsLayer.with_filter(devtools_event_filter))
.with(fmt::layer().event_format(DevtoolsFormatter))
.init();
.try_init()
.ok();
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@sapphi-red
Copy link
Copy Markdown
Member

refs #8698 (comment)

@sapphi-red
Copy link
Copy Markdown
Member

I guess this would disable the debug file output is used by the devtools feature and will break the feature.

@fengmk2
Copy link
Copy Markdown
Author

fengmk2 commented Apr 13, 2026

@sapphi-red Indeed, we need to think of a better solution. Do you have any suggestions?

@fengmk2 fengmk2 marked this pull request as draft April 13, 2026 02:48
@sapphi-red
Copy link
Copy Markdown
Member

I'm not familiar with the code around here and not sure how it's working in details. @hyf0 Do you have any ideas?

@fengmk2
Copy link
Copy Markdown
Author

fengmk2 commented Apr 15, 2026

After communicating with @hyf0, I will handle it at the Vite+ side for now to avoid simultaneous registration. The long-term solution needs @hyf0 to take another look.

@fengmk2 fengmk2 closed this Apr 15, 2026
@hyf0
Copy link
Copy Markdown
Member

hyf0 commented Apr 15, 2026

For the future plan:

#### `tracing` Scoped Subscriber Mechanisms

Should be able to solve it without causing conflict with other crates.

graphite-app Bot pushed a commit to voidzero-dev/vite-plus that referenced this pull request Apr 16, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants