When concurrency touches shared state, ensure (a) mutexes don’t guard blocking I/O, (b) slow/blocking operations run on dedicated worker threads, (c) state cleanup is race-safe, and (d) any external probe has a timeout and non-fatal fallback.
Practical rules 1) Don’t hold locks across blocking calls
write_all(...) while holding the shared sessions mutex.2) Offload long/contended I/O to worker threads
terminal_write (or similar request handlers) should only enqueue work (e.g., via mpsc), so the command/IPC/UI thread stays responsive.3) Make cleanup/removal race-safe
4) Never block critical threads on external probes; use timeouts
5) Guard lifecycle concurrency at the app level
Illustrative pattern (writer worker + enqueue)
use std::{sync::{Arc, Mutex, mpsc}, thread};
struct Session { tx: mpsc::Sender<Vec<u8>> }
fn start_session(shared: Arc<Mutex<std::collections::HashMap<String, Session>>>, id: String) {
let (tx, rx) = mpsc::channel::<Vec<u8>>();
// Writer worker: only this thread performs blocking writes.
thread::spawn(move || {
while let Ok(bytes) = rx.recv() {
// blocking I/O here, not under shared locks
// pty_writer.write_all(&bytes).ok();
}
// tx dropped => worker ends
});
shared.lock().unwrap().insert(id, Session { tx });
}
fn terminal_write(shared: &Arc<Mutex<std::collections::HashMap<String, Session>>>, id: &str, data: Vec<u8>) {
// Lock only long enough to grab the sender.
let tx = { shared.lock().unwrap().get(id).unwrap().tx.clone() };
let _ = tx.send(data); // enqueue; never block here
}
Use this standard to prevent UI/IPC stalls, avoid lock-induced deadlocks/perf cliffs, and eliminate stale-thread state races.