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
|
//! spawn / kill plan are real and tested here so the live render slots into a
|
||||||
//! known shape.
|
//! known shape.
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
use crate::hypr::Client;
|
use crate::hypr::Client;
|
||||||
|
|
||||||
const OVERLAY_W: i32 = 96;
|
const OVERLAY_W: i32 = 96;
|
||||||
@ -66,12 +68,23 @@ pub fn live_enabled() -> bool {
|
|||||||
pub struct OverlayHandle {
|
pub struct OverlayHandle {
|
||||||
pub slot: u32,
|
pub slot: u32,
|
||||||
pub rect: OverlayRect,
|
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 {
|
impl OverlayHandle {
|
||||||
/// Stub handle for non-live paths. Does not connect to Wayland.
|
/// Stub handle for non-live paths. Does not connect to Wayland.
|
||||||
pub fn stub(slot: u32, rect: OverlayRect) -> Self {
|
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);
|
let sock = ipc_sock.unwrap_or_else(crate::session::default_sock);
|
||||||
match crate::wayland_layer::spawn(slot, rect, 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) => {
|
Err(e) => {
|
||||||
tracing::warn!("overlay: live spawn failed for slot {slot}: {e}");
|
tracing::warn!("overlay: live spawn failed for slot {slot}: {e}");
|
||||||
OverlayHandle::stub(slot, rect)
|
OverlayHandle::stub(slot, rect)
|
||||||
@ -151,7 +164,9 @@ impl OverlayHub {
|
|||||||
) -> Vec<OverlayHandle> {
|
) -> Vec<OverlayHandle> {
|
||||||
let (to_spawn, kill) = self.plan(slots);
|
let (to_spawn, kill) = self.plan(slots);
|
||||||
for k in kill {
|
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 {
|
for h in &to_spawn {
|
||||||
self.by_slot.insert(h.slot, h.clone());
|
self.by_slot.insert(h.slot, h.clone());
|
||||||
@ -168,7 +183,14 @@ impl OverlayHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill_all(&mut self) {
|
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
|
//! commit and listens for clicks. Per T9 the badge is readable (3x5
|
||||||
//! digits, ARGB8888, opaque background, solid foreground).
|
//! digits, ARGB8888, opaque background, solid foreground).
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
use crate::overlay::OverlayRect;
|
use crate::overlay::OverlayRect;
|
||||||
use crate::profile::runtime_dir;
|
use crate::profile::runtime_dir;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -160,11 +162,18 @@ struct OverlayState {
|
|||||||
pub struct LiveOverlayHandle {
|
pub struct LiveOverlayHandle {
|
||||||
pub slot: u32,
|
pub slot: u32,
|
||||||
pub rect: OverlayRect,
|
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<()>>,
|
join: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LiveOverlayHandle {
|
impl LiveOverlayHandle {
|
||||||
|
/// Ask the thread to exit, then join it. Idempotent.
|
||||||
pub fn shutdown(mut self) {
|
pub fn shutdown(mut self) {
|
||||||
|
self.stop.store(true, Ordering::Relaxed);
|
||||||
if let Some(j) = self.join.take() {
|
if let Some(j) = self.join.take() {
|
||||||
let _ = j.join();
|
let _ = j.join();
|
||||||
}
|
}
|
||||||
@ -177,20 +186,23 @@ impl LiveOverlayHandle {
|
|||||||
/// surface. Errors during connect are returned; setup errors after
|
/// surface. Errors during connect are returned; setup errors after
|
||||||
/// connect are logged and the thread exits.
|
/// connect are logged and the thread exits.
|
||||||
pub fn spawn(slot: u32, rect: OverlayRect, ipc_sock: PathBuf) -> anyhow::Result<LiveOverlayHandle> {
|
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()
|
let join = std::thread::Builder::new()
|
||||||
.name(format!("enboxer-overlay-{slot}"))
|
.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"),
|
Ok(()) => tracing::debug!("overlay slot {slot} exited cleanly"),
|
||||||
Err(e) => tracing::warn!("overlay slot {slot}: {e}"),
|
Err(e) => tracing::warn!("overlay slot {slot}: {e}"),
|
||||||
})?;
|
})?;
|
||||||
Ok(LiveOverlayHandle {
|
Ok(LiveOverlayHandle {
|
||||||
slot,
|
slot,
|
||||||
rect,
|
rect,
|
||||||
|
stop,
|
||||||
join: Some(join),
|
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 conn = Connection::connect_to_env()?;
|
||||||
let display = conn.display();
|
let display = conn.display();
|
||||||
let mut event_queue = conn.new_event_queue::<OverlayState>();
|
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());
|
state.buffer = Some(buffer.clone());
|
||||||
surface.attach(Some(&buffer), 0, 0);
|
surface.attach(Some(&buffer), 0, 0);
|
||||||
surface.commit();
|
surface.commit();
|
||||||
while !state.exited {
|
while !state.exited && !stop.load(Ordering::Relaxed) {
|
||||||
if let Err(e) = event_queue.blocking_dispatch(&mut state) {
|
if let Err(e) = event_queue.blocking_dispatch(&mut state) {
|
||||||
tracing::warn!("overlay slot {slot}: dispatch: {e}");
|
tracing::warn!("overlay slot {slot}: dispatch: {e}");
|
||||||
break;
|
break;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user