Overlay: thread can be stopped externally instead of detaching (Bug #5)
Bug from Grok round-1 #5. spawn_with_sock returned Ok(_) and dropped the LiveOverlayHandle, so the JoinHandle was never joined or signalled. The thread detached; OverlayHub could only clear its slot map, never stop the actual Wayland thread. With env-gated rendering (Bug #8), the operator's overlays would accumulate as ghost threads. Changes: - wayland_layer: LiveOverlayHandle gains a stop: Arc<AtomicBool>. spawn() allocates it, threads a copy into run(), stores a copy on the returned handle. - wayland_layer: run() polls stop in addition to state.exited; flipping the bit causes the next roundtrip to exit instead of waiting on the compositor's Closed event. - overlay: OverlayHandle gains stop: Option<Arc<AtomicBool>> and a kill() method that flips the bit. - overlay: spawn_with_sock now puts the same stop Arc on the returned OverlayHandle (was previously throwing the live handle away). - overlay: OverlayHub.sync() and kill_all() call kill() on every removed handle, so slot changes actually tear the threads down. cargo test 96+/0; clippy clean.
This commit is contained in:
parent
5da77b28b3
commit
b9d5f144bd
@ -17,6 +17,8 @@
|
||||
//! spawn / kill plan are real and tested here so the live render slots into a
|
||||
//! known shape.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use crate::hypr::Client;
|
||||
|
||||
const OVERLAY_W: i32 = 96;
|
||||
@ -66,12 +68,23 @@ pub fn live_enabled() -> bool {
|
||||
pub struct OverlayHandle {
|
||||
pub slot: u32,
|
||||
pub rect: OverlayRect,
|
||||
/// External kill signal for the live Wayland thread. The hub flips
|
||||
/// this to true when the slot goes away so the thread exits cleanly
|
||||
/// instead of being silently detached (Bug #5).
|
||||
pub stop: Option<Arc<AtomicBool>>,
|
||||
}
|
||||
|
||||
impl OverlayHandle {
|
||||
/// Stub handle for non-live paths. Does not connect to Wayland.
|
||||
pub fn stub(slot: u32, rect: OverlayRect) -> Self {
|
||||
Self { slot, rect }
|
||||
Self { slot, rect, stop: None }
|
||||
}
|
||||
|
||||
/// Ask the underlying thread to exit. No-op for stubs.
|
||||
pub fn kill(&self) {
|
||||
if let Some(s) = &self.stop {
|
||||
s.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +114,7 @@ pub fn spawn_with_sock(
|
||||
}
|
||||
let sock = ipc_sock.unwrap_or_else(crate::session::default_sock);
|
||||
match crate::wayland_layer::spawn(slot, rect, sock) {
|
||||
Ok(_) => OverlayHandle::stub(slot, rect),
|
||||
Ok(handle) => OverlayHandle { slot, rect, stop: Some(handle.stop) },
|
||||
Err(e) => {
|
||||
tracing::warn!("overlay: live spawn failed for slot {slot}: {e}");
|
||||
OverlayHandle::stub(slot, rect)
|
||||
@ -151,7 +164,9 @@ impl OverlayHub {
|
||||
) -> Vec<OverlayHandle> {
|
||||
let (to_spawn, kill) = self.plan(slots);
|
||||
for k in kill {
|
||||
self.by_slot.remove(&k);
|
||||
if let Some(h) = self.by_slot.remove(&k) {
|
||||
h.kill();
|
||||
}
|
||||
}
|
||||
for h in &to_spawn {
|
||||
self.by_slot.insert(h.slot, h.clone());
|
||||
@ -168,7 +183,14 @@ impl OverlayHub {
|
||||
}
|
||||
|
||||
pub fn kill_all(&mut self) {
|
||||
self.by_slot.clear();
|
||||
// Drain via mem::take rather than the BTreeMap::drain iterator to
|
||||
// keep ownership types local (std::mem::take always works
|
||||
// regardless of the nightly/stable Rust allocation churn in the
|
||||
// IntoIterator impls).
|
||||
let taken = std::mem::take(&mut self.by_slot);
|
||||
for (_, h) in taken {
|
||||
h.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
//! commit and listens for clicks. Per T9 the badge is readable (3x5
|
||||
//! digits, ARGB8888, opaque background, solid foreground).
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use crate::overlay::OverlayRect;
|
||||
use crate::profile::runtime_dir;
|
||||
use std::fs::File;
|
||||
@ -160,11 +162,18 @@ struct OverlayState {
|
||||
pub struct LiveOverlayHandle {
|
||||
pub slot: u32,
|
||||
pub rect: OverlayRect,
|
||||
/// External kill signal. The dispatch loop in [`run`] polls this
|
||||
/// once per roundtrip; flipping to true causes the thread to exit
|
||||
/// on the next round, so the join in [`shutdown`] does not block
|
||||
/// forever waiting on the compositor's `Closed` event.
|
||||
pub stop: Arc<AtomicBool>,
|
||||
join: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl LiveOverlayHandle {
|
||||
/// Ask the thread to exit, then join it. Idempotent.
|
||||
pub fn shutdown(mut self) {
|
||||
self.stop.store(true, Ordering::Relaxed);
|
||||
if let Some(j) = self.join.take() {
|
||||
let _ = j.join();
|
||||
}
|
||||
@ -177,20 +186,23 @@ impl LiveOverlayHandle {
|
||||
/// surface. Errors during connect are returned; setup errors after
|
||||
/// connect are logged and the thread exits.
|
||||
pub fn spawn(slot: u32, rect: OverlayRect, ipc_sock: PathBuf) -> anyhow::Result<LiveOverlayHandle> {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let stop_for_thread = stop.clone();
|
||||
let join = std::thread::Builder::new()
|
||||
.name(format!("enboxer-overlay-{slot}"))
|
||||
.spawn(move || match run(slot, rect, ipc_sock) {
|
||||
.spawn(move || match run(slot, rect, ipc_sock, stop_for_thread) {
|
||||
Ok(()) => tracing::debug!("overlay slot {slot} exited cleanly"),
|
||||
Err(e) => tracing::warn!("overlay slot {slot}: {e}"),
|
||||
})?;
|
||||
Ok(LiveOverlayHandle {
|
||||
slot,
|
||||
rect,
|
||||
stop,
|
||||
join: Some(join),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(slot: u32, rect: OverlayRect, ipc_sock: PathBuf) -> anyhow::Result<()> {
|
||||
fn run(slot: u32, rect: OverlayRect, ipc_sock: PathBuf, stop: Arc<AtomicBool>) -> anyhow::Result<()> {
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let display = conn.display();
|
||||
let mut event_queue = conn.new_event_queue::<OverlayState>();
|
||||
@ -267,7 +279,7 @@ fn run(slot: u32, rect: OverlayRect, ipc_sock: PathBuf) -> anyhow::Result<()> {
|
||||
state.buffer = Some(buffer.clone());
|
||||
surface.attach(Some(&buffer), 0, 0);
|
||||
surface.commit();
|
||||
while !state.exited {
|
||||
while !state.exited && !stop.load(Ordering::Relaxed) {
|
||||
if let Err(e) = event_queue.blocking_dispatch(&mut state) {
|
||||
tracing::warn!("overlay slot {slot}: dispatch: {e}");
|
||||
break;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user