Skip to content

Commit aea9b4a

Browse files
Simplified backtraces (#12305)
# Objective Remove Bevy internals from backtraces ## Solution Executors insert `__rust_begin_short_backtrace` into the callstack before running a system. <details> <summary>Example current output</summary> ``` thread 'Compute Task Pool (3)' panicked at src/main.rs:7:33: Foo stack backtrace: 0: rust_begin_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:647:5 1: core::panicking::panic_fmt at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panicking.rs:72:14 2: foo::main::{{closure}} at ./src/main.rs:7:33 3: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:294:13 4: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run::call_inner at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:661:21 5: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:664:17 6: <bevy_ecs::system::function_system::FunctionSystem<Marker,F> as bevy_ecs::system::system::System>::run_unsafe at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:504:19 7: bevy_ecs::schedule::executor::multi_threaded::ExecutorState::spawn_system_task::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:621:26 8: core::ops::function::FnOnce::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:250:5 9: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:272:9 10: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 11: __rust_try 12: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 13: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 14: bevy_ecs::schedule::executor::multi_threaded::ExecutorState::spawn_system_task::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:614:23 15: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::future::future::Future>::poll at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:297:9 16: <futures_lite::future::CatchUnwind<F> as core::future::future::Future>::poll::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:588:42 17: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:272:9 18: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 19: __rust_try 20: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 21: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 22: <futures_lite::future::CatchUnwind<F> as core::future::future::Future>::poll at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:588:9 23: async_executor::Executor::spawn::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:158:20 24: async_task::raw::RawTask<F,T,S,M>::run::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/raw.rs:550:21 25: core::ops::function::FnOnce::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:250:5 26: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panic/unwind_safe.rs:272:9 27: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 28: __rust_try 29: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 30: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 31: async_task::raw::RawTask<F,T,S,M>::run at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/raw.rs:549:23 32: async_task::runnable::Runnable<M>::run at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-task-4.7.0/src/runnable.rs:781:18 33: async_executor::Executor::run::{{closure}}::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:254:21 34: <futures_lite::future::Or<F1,F2> as core::future::future::Future>::poll at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:449:33 35: async_executor::Executor::run::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/async-executor-1.8.0/src/lib.rs:261:32 36: futures_lite::future::block_on::{{closure}} at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:99:19 37: std::thread::local::LocalKey<T>::try_with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:286:16 38: std::thread::local::LocalKey<T>::with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:262:9 39: futures_lite::future::block_on at /home/vj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:78:5 40: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_tasks/src/task_pool.rs:180:37 41: std::panicking::try::do_call at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:554:40 42: __rust_try 43: std::panicking::try at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:518:19 44: std::panic::catch_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panic.rs:142:14 45: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_tasks/src/task_pool.rs:174:43 46: std::thread::local::LocalKey<T>::try_with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:286:16 47: std::thread::local::LocalKey<T>::with at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/thread/local.rs:262:9 48: bevy_tasks::task_pool::TaskPool::new_internal::{{closure}}::{{closure}} at /home/vj/workspace/rust/bevy/crates/bevy_tasks/src/task_pool.rs:167:25 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. Encountered a panic in system `foo::main::{{closure}}`! Encountered a panic in system `bevy_app::main_schedule::Main::run_main`! get on your knees and beg mommy for forgiveness you pervert~ 💖 ``` </details> <details> <summary>Example output with this PR</summary> ``` Panic at src/main.rs:7:33: Foo stack backtrace: 0: rust_begin_unwind at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/std/src/panicking.rs:647:5 1: core::panicking::panic_fmt at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/panicking.rs:72:14 2: foo::main::{{closure}} at ./src/main.rs:7:59 3: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut at /rustc/8ace7ea1f7cbba7b4f031e66c54ca237a0d65de6/library/core/src/ops/function.rs:294:13 4: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run::call_inner at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:661:21 5: <Func as bevy_ecs::system::function_system::SystemParamFunction<fn() .> Out>>::run at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:664:17 6: <bevy_ecs::system::function_system::FunctionSystem<Marker,F> as bevy_ecs::system::system::System>::run_unsafe at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/system/function_system.rs:504:19 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. Encountered a panic in system `foo::main::{{closure}}`! Encountered a panic in system `bevy_app::main_schedule::Main::run_main`! ``` </details> Full backtraces (`RUST_BACKTRACE=full`) are unchanged. ## Alternative solutions Write a custom panic hook. This could potentially let use exclude a few more callstack frames but requires a dependency on `backtrace` and is incompatible with user-provided panic hooks. --- ## Changelog - Backtraces now exclude many Bevy internals (unless `RUST_BACKTRACE=full` is used) --------- Co-authored-by: James Liu <[email protected]>
1 parent bc4d8bb commit aea9b4a

File tree

5 files changed

+85
-18
lines changed

5 files changed

+85
-18
lines changed

crates/bevy_ecs/src/schedule/executor/mod.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,54 @@ pub(super) fn is_apply_deferred(system: &BoxedSystem) -> bool {
119119
// deref to use `System::type_id` instead of `Any::type_id`
120120
system.as_ref().type_id() == apply_deferred.system_type_id()
121121
}
122+
123+
/// These functions hide the bottom of the callstack from `RUST_BACKTRACE=1` (assuming the default panic handler is used).
124+
/// The full callstack will still be visible with `RUST_BACKTRACE=full`.
125+
/// They are specialized for `System::run` & co instead of being generic over closures because this avoids an
126+
/// extra frame in the backtrace.
127+
///
128+
/// This is reliant on undocumented behavior in Rust's default panic handler, which checks the call stack for symbols
129+
/// containing the string `__rust_begin_short_backtrace` in their mangled name.
130+
mod __rust_begin_short_backtrace {
131+
use std::hint::black_box;
132+
133+
use crate::{
134+
system::{ReadOnlySystem, System},
135+
world::{unsafe_world_cell::UnsafeWorldCell, World},
136+
};
137+
138+
/// # Safety
139+
/// See `System::run_unsafe`.
140+
#[inline(never)]
141+
pub(super) unsafe fn run_unsafe(
142+
system: &mut dyn System<In = (), Out = ()>,
143+
world: UnsafeWorldCell,
144+
) {
145+
system.run_unsafe((), world);
146+
black_box(());
147+
}
148+
149+
/// # Safety
150+
/// See `ReadOnlySystem::run_unsafe`.
151+
#[inline(never)]
152+
pub(super) unsafe fn readonly_run_unsafe<O: 'static>(
153+
system: &mut dyn ReadOnlySystem<In = (), Out = O>,
154+
world: UnsafeWorldCell,
155+
) -> O {
156+
black_box(system.run_unsafe((), world))
157+
}
158+
159+
#[inline(never)]
160+
pub(super) fn run(system: &mut dyn System<In = (), Out = ()>, world: &mut World) {
161+
system.run((), world);
162+
black_box(());
163+
}
164+
165+
#[inline(never)]
166+
pub(super) fn readonly_run<O: 'static>(
167+
system: &mut dyn ReadOnlySystem<In = (), Out = O>,
168+
world: &mut World,
169+
) -> O {
170+
black_box(system.run((), world))
171+
}
172+
}

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use crate::{
2424

2525
use crate as bevy_ecs;
2626

27+
use super::__rust_begin_short_backtrace;
28+
2729
/// Borrowed data used by the [`MultiThreadedExecutor`].
2830
struct Environment<'env, 'sys> {
2931
executor: &'env MultiThreadedExecutor,
@@ -618,7 +620,12 @@ impl ExecutorState {
618620
// - The caller ensures that we have permission to
619621
// access the world data used by the system.
620622
// - `update_archetype_component_access` has been called.
621-
unsafe { system.run_unsafe((), context.environment.world_cell) };
623+
unsafe {
624+
__rust_begin_short_backtrace::run_unsafe(
625+
&mut **system,
626+
context.environment.world_cell,
627+
);
628+
};
622629
}));
623630
context.system_completed(system_index, res, system);
624631
};
@@ -668,7 +675,7 @@ impl ExecutorState {
668675
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
669676
#[cfg(feature = "trace")]
670677
let _span = system_span.enter();
671-
system.run((), world);
678+
__rust_begin_short_backtrace::run(&mut **system, world);
672679
}));
673680
context.system_completed(system_index, res, system);
674681
};
@@ -780,7 +787,7 @@ unsafe fn evaluate_and_fold_conditions(
780787
.map(|condition| {
781788
// SAFETY: The caller ensures that `world` has permission to
782789
// access any data required by the condition.
783-
unsafe { condition.run_unsafe((), world) }
790+
unsafe { __rust_begin_short_backtrace::readonly_run_unsafe(&mut **condition, world) }
784791
})
785792
.fold(true, |acc, res| acc && res)
786793
}

crates/bevy_ecs/src/schedule/executor/simple.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use crate::{
1010
world::World,
1111
};
1212

13+
use super::__rust_begin_short_backtrace;
14+
1315
/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls
1416
/// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system.
1517
#[derive(Default)]
@@ -93,7 +95,7 @@ impl SystemExecutor for SimpleExecutor {
9395
}
9496

9597
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
96-
system.run((), world);
98+
__rust_begin_short_backtrace::run(&mut **system, world);
9799
}));
98100
if let Err(payload) = res {
99101
eprintln!("Encountered a panic in system `{}`!", &*system.name());
@@ -126,7 +128,7 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
126128
#[allow(clippy::unnecessary_fold)]
127129
conditions
128130
.iter_mut()
129-
.map(|condition| condition.run((), world))
131+
.map(|condition| __rust_begin_short_backtrace::readonly_run(&mut **condition, world))
130132
.fold(true, |acc, res| acc && res)
131133
}
132134

crates/bevy_ecs/src/schedule/executor/single_threaded.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use crate::{
88
world::World,
99
};
1010

11+
use super::__rust_begin_short_backtrace;
12+
1113
/// Runs the schedule using a single thread.
1214
///
1315
/// Useful if you're dealing with a single-threaded environment, saving your threads for
@@ -101,14 +103,14 @@ impl SystemExecutor for SingleThreadedExecutor {
101103

102104
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
103105
if system.is_exclusive() {
104-
system.run((), world);
106+
__rust_begin_short_backtrace::run(&mut **system, world);
105107
} else {
106108
// Use run_unsafe to avoid immediately applying deferred buffers
107109
let world = world.as_unsafe_world_cell();
108110
system.update_archetype_component_access(world);
109111
// SAFETY: We have exclusive, single-threaded access to the world and
110112
// update_archetype_component_access is being called immediately before this.
111-
unsafe { system.run_unsafe((), world) };
113+
unsafe { __rust_begin_short_backtrace::run_unsafe(&mut **system, world) };
112114
}
113115
}));
114116
if let Err(payload) = res {
@@ -158,6 +160,6 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W
158160
#[allow(clippy::unnecessary_fold)]
159161
conditions
160162
.iter_mut()
161-
.map(|condition| condition.run((), world))
163+
.map(|condition| __rust_begin_short_backtrace::readonly_run(&mut **condition, world))
162164
.fold(true, |acc, res| acc && res)
163165
}

crates/bevy_render/src/pipelined_rendering.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use async_channel::{Receiver, Sender};
22

3-
use bevy_app::{App, AppLabel, Main, Plugin, SubApp};
3+
use bevy_app::{App, AppExit, AppLabel, Main, Plugin, SubApp};
44
use bevy_ecs::{
55
schedule::MainThreadExecutor,
66
system::Resource,
@@ -45,10 +45,11 @@ impl RenderAppChannels {
4545
}
4646

4747
/// Receive the `render_app` from the rendering thread.
48-
pub async fn recv(&mut self) -> SubApp {
49-
let render_app = self.render_to_app_receiver.recv().await.unwrap();
48+
/// Return `None` if the render thread has panicked.
49+
pub async fn recv(&mut self) -> Option<SubApp> {
50+
let render_app = self.render_to_app_receiver.recv().await.ok()?;
5051
self.render_app_in_render_thread = false;
51-
render_app
52+
Some(render_app)
5253
}
5354
}
5455

@@ -180,16 +181,20 @@ fn update_rendering(app_world: &mut World, _sub_app: &mut App) {
180181
world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| {
181182
// we use a scope here to run any main thread tasks that the render world still needs to run
182183
// while we wait for the render world to be received.
183-
let mut render_app = ComputeTaskPool::get()
184+
if let Some(mut render_app) = ComputeTaskPool::get()
184185
.scope_with_executor(true, Some(&*main_thread_executor.0), |s| {
185186
s.spawn(async { render_channels.recv().await });
186187
})
187188
.pop()
188-
.unwrap();
189-
190-
render_app.extract(world);
191-
192-
render_channels.send_blocking(render_app);
189+
.unwrap()
190+
{
191+
render_app.extract(world);
192+
193+
render_channels.send_blocking(render_app);
194+
} else {
195+
// Renderer thread panicked
196+
world.send_event(AppExit);
197+
}
193198
});
194199
});
195200
}

0 commit comments

Comments
 (0)