Skip to content

puffin_egui deadlock if new_frame is called from another thread #197

@lunixbochs

Description

@lunixbochs

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:

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:

view_clone.lock().add_frame(frame);

This mismatched lock order triggers a deadlock.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions