fix(kms): join surface threads before exit to avoid SIGABRT on shutdown
When the kiosk child exits, calling process::exit() directly from the calloop
callback runs libc's atexit handlers (Mesa's util_queue_kill_threads) while a
KMS surface thread may still be mid-frame in eglCreateSync(). Concurrent Mesa
teardown and live EGL use corrupt the heap and glibc aborts the process
("free(): corrupted unsorted chunks", SIGABRT).
Stop the event loop cleanly via should_stop instead, stashing the child's exit
code in Common::kiosk_exit_code. After the loop returns, pause() each DRM device
(releasing master so the surface drop path skips its blocking clear_state(), and
the incoming compositor gets master immediately), then drop_and_join() every
surface so no thread is left in Mesa. Only then is process::exit() called, at
which point atexit is safe to run.
The join is done here rather than in Surface::Drop because an unconditional join
in Drop deadlocks against apply_config_for_outputs during normal operation; once
the event loop has stopped, the surface thread can process ThreadCommand::End
and exit without the main thread servicing any pending message.
https://github.com/pop-os/cosmic-comp/issues/2375
Authored by Claude Opus 4.8
Reviewed by Timo Strunk <Timo.Strunk@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9718699ec7
commit
53a8d0bea2
2 changed files with 30 additions and 8 deletions
36
src/lib.rs
36
src/lib.rs
|
|
@ -16,7 +16,7 @@ use smithay::{
|
|||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use state::{LastRefresh, State};
|
||||
use state::{BackendData, LastRefresh, State};
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
|
|
@ -222,19 +222,17 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
// Kiosk child exited with status
|
||||
Ok(Some(exit_status)) => {
|
||||
info!("Command exited with status {:?}", exit_status);
|
||||
match exit_status.code() {
|
||||
// Exiting with the same status as the kiosk child
|
||||
Some(code) => process::exit(code),
|
||||
// The kiosk child exited with signal, exiting with error
|
||||
None => process::exit(1),
|
||||
}
|
||||
// Stop cleanly so surface threads are joined before exit() (signal -> 1).
|
||||
state.common.kiosk_exit_code = Some(exit_status.code().unwrap_or(1));
|
||||
state.common.should_stop = true;
|
||||
}
|
||||
// Command still running
|
||||
Ok(None) => {}
|
||||
// Kiosk child disappeared, exiting with error
|
||||
Err(err) => {
|
||||
warn!(?err, "Failed to wait for command");
|
||||
process::exit(1);
|
||||
state.common.kiosk_exit_code = Some(1);
|
||||
state.common.should_stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -245,10 +243,32 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
let _ = child.kill();
|
||||
}
|
||||
|
||||
let kiosk_exit_code = state.common.kiosk_exit_code;
|
||||
|
||||
// Join surface threads before exit() so no thread is mid-eglCreateSync when
|
||||
// Mesa's atexit handlers run and corrupt the heap (issue #2375). Safe here
|
||||
// because the event loop has stopped; an unconditional join in Surface::Drop
|
||||
// would instead deadlock against apply_config_for_outputs.
|
||||
if let BackendData::Kms(kms) = &mut state.backend {
|
||||
// Release master first so the surface drop path skips its blocking commit.
|
||||
for device in kms.drm_devices.values_mut() {
|
||||
device.drm.pause();
|
||||
}
|
||||
for device in kms.drm_devices.values_mut() {
|
||||
for (_, surface) in device.inner.surfaces.drain() {
|
||||
surface.drop_and_join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drop eventloop & state before logger
|
||||
std::mem::drop(event_loop);
|
||||
std::mem::drop(state);
|
||||
|
||||
if let Some(code) = kiosk_exit_code {
|
||||
process::exit(code);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ pub struct Common {
|
|||
pub clock: Clock<Monotonic>,
|
||||
pub startup_done: Arc<AtomicBool>,
|
||||
pub should_stop: bool,
|
||||
pub kiosk_exit_code: Option<i32>,
|
||||
|
||||
pub gesture_state: Option<GestureState>,
|
||||
|
||||
|
|
@ -754,6 +755,7 @@ impl State {
|
|||
clock,
|
||||
startup_done: Arc::new(AtomicBool::new(false)),
|
||||
should_stop: false,
|
||||
kiosk_exit_code: None,
|
||||
gesture_state: None,
|
||||
|
||||
kiosk_child: None,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue