-
Notifications
You must be signed in to change notification settings - Fork 98
Description
I'm running into a deadlock in puffin_egui if new_frame() is called from a thread other than the UI thread.
I built a sampling profiler that runs in a single thread, manually creates ThreadProfiler instances for all threads running in the process, and samples their stack traces at intervals, so I'm calling new_frame from that thread rather than my UI thread.
Here puffin takes the GlobalFrameView lock and holds it across the UI function call:
Lines 117 to 130 in 0dd10b2
| pub fn window(&mut self, ctx: &egui::Context) -> bool { | |
| let mut frame_view = self.global_frame_view.lock(); | |
| self.profiler_ui | |
| .window(ctx, &mut MaybeMutRef::MutRef(&mut frame_view)) | |
| } | |
| /// Show the profiler. | |
| /// | |
| /// Call this from within an [`egui::Window`], or use [`Self::window`] instead. | |
| pub fn ui(&mut self, ui: &mut egui::Ui) { | |
| let mut frame_view = self.global_frame_view.lock(); | |
| self.profiler_ui | |
| .ui(ui, &mut MaybeMutRef::MutRef(&mut frame_view)); | |
| } |
The profiler ui call itself contains profiling calls, which calls ThreadProfiler::end_scope internally, which tries to take the GlobalProfiler lock. So you have a nested GlobalFrameView lock -> GlobalProfiler lock acquisition in the UI.
GlobalProfiler::lock().new_frame() has the GlobalProfiler locked and calls the sink function here, which tries to lock the GlobalFrameView:
puffin/puffin/src/profile_view.rs
Line 259 in 0dd10b2
| view_clone.lock().add_frame(frame); |
This mismatched lock order triggers a deadlock.