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 anyhow::{Context, Result};
|
||||||
use state::{LastRefresh, State};
|
use state::{BackendData, LastRefresh, State};
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
|
|
@ -222,19 +222,17 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
||||||
// Kiosk child exited with status
|
// Kiosk child exited with status
|
||||||
Ok(Some(exit_status)) => {
|
Ok(Some(exit_status)) => {
|
||||||
info!("Command exited with status {:?}", exit_status);
|
info!("Command exited with status {:?}", exit_status);
|
||||||
match exit_status.code() {
|
// Stop cleanly so surface threads are joined before exit() (signal -> 1).
|
||||||
// Exiting with the same status as the kiosk child
|
state.common.kiosk_exit_code = Some(exit_status.code().unwrap_or(1));
|
||||||
Some(code) => process::exit(code),
|
state.common.should_stop = true;
|
||||||
// The kiosk child exited with signal, exiting with error
|
|
||||||
None => process::exit(1),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Command still running
|
// Command still running
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
// Kiosk child disappeared, exiting with error
|
// Kiosk child disappeared, exiting with error
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(?err, "Failed to wait for command");
|
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 _ = 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
|
// drop eventloop & state before logger
|
||||||
std::mem::drop(event_loop);
|
std::mem::drop(event_loop);
|
||||||
std::mem::drop(state);
|
std::mem::drop(state);
|
||||||
|
|
||||||
|
if let Some(code) = kiosk_exit_code {
|
||||||
|
process::exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,7 @@ pub struct Common {
|
||||||
pub clock: Clock<Monotonic>,
|
pub clock: Clock<Monotonic>,
|
||||||
pub startup_done: Arc<AtomicBool>,
|
pub startup_done: Arc<AtomicBool>,
|
||||||
pub should_stop: bool,
|
pub should_stop: bool,
|
||||||
|
pub kiosk_exit_code: Option<i32>,
|
||||||
|
|
||||||
pub gesture_state: Option<GestureState>,
|
pub gesture_state: Option<GestureState>,
|
||||||
|
|
||||||
|
|
@ -754,6 +755,7 @@ impl State {
|
||||||
clock,
|
clock,
|
||||||
startup_done: Arc::new(AtomicBool::new(false)),
|
startup_done: Arc::new(AtomicBool::new(false)),
|
||||||
should_stop: false,
|
should_stop: false,
|
||||||
|
kiosk_exit_code: None,
|
||||||
gesture_state: None,
|
gesture_state: None,
|
||||||
|
|
||||||
kiosk_child: None,
|
kiosk_child: None,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue