fix: resolve mutex deadlock in console/GUI mode

This commit is contained in:
Jan-Peter von Hunnius 2026-02-27 07:24:59 +01:00
parent 9d328ee87b
commit d1cc176cc7
3 changed files with 132 additions and 50 deletions

View file

@ -142,8 +142,8 @@ impl ConsoleApp {
while self.running {
let now = Instant::now();
// Handle input
if let Ok(true) = event::poll(Duration::from_millis(10)) {
// Handle input with longer poll interval to reduce CPU usage
if let Ok(true) = event::poll(Duration::from_millis(50)) {
if let Ok(Event::Key(key)) = event::read() {
self.handle_key_event(key.code, key.modifiers);
}
@ -158,20 +158,30 @@ impl ConsoleApp {
last_draw = now;
}
// Check if system should still be running
if let Ok(system) = self.system.lock() {
if !system.is_running() && !self.show_help {
// System stopped, show final state briefly
if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
eprintln!("DEBUG: Failed to draw final UI: {}", e);
// Check if system should still be running using try_lock to avoid blocking
match self.system.try_lock() {
Ok(system) => {
if !system.is_running() && !self.show_help {
// System stopped, show final state briefly
if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
eprintln!("DEBUG: Failed to draw final UI: {}", e);
}
thread::sleep(Duration::from_millis(500));
break;
}
thread::sleep(Duration::from_millis(500));
}
Err(std::sync::TryLockError::WouldBlock) => {
// System is busy, continue running - this is expected
}
Err(std::sync::TryLockError::Poisoned(_)) => {
// System mutex is poisoned, exit gracefully
eprintln!("DEBUG: System mutex poisoned, exiting");
break;
}
}
// Small delay to prevent busy waiting
thread::sleep(Duration::from_millis(1));
thread::sleep(Duration::from_millis(10));
}
// Restore terminal
@ -464,10 +474,11 @@ impl ConsoleApp {
])
.split(area);
// System information
let system_info = match self.system.lock() {
Ok(system) => match system.get_system_info() {
info => vec![
// System information - use try_lock to avoid blocking
let system_info = match self.system.try_lock() {
Ok(system) => {
let info = system.get_system_info();
vec![
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
Line::from(vec![Span::raw(format!(
"Description: {}",
@ -487,8 +498,13 @@ impl ConsoleApp {
info.ram_size
))]),
Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]),
],
},
]
}
Err(std::sync::TryLockError::WouldBlock) => {
vec![Line::from(vec![Span::raw(
"System busy (emulation running)...",
)])]
}
Err(_) => {
vec![Line::from(vec![Span::raw(
"System information unavailable",
@ -526,7 +542,7 @@ impl ConsoleApp {
fn get_cpu_register_info(&self) -> Vec<Line<'static>> {
let mut register_info = vec![Line::from(vec![Span::raw("CPU Registers:")])];
match self.system.lock() {
match self.system.try_lock() {
Ok(system) => {
// Try to find Intel 4004 CPU and get register data
let cpu_data = system.get_cpu_register_data();
@ -567,6 +583,11 @@ impl ConsoleApp {
register_info.push(Line::from(vec![Span::raw("No Intel 4004 CPU found")]));
}
}
Err(std::sync::TryLockError::WouldBlock) => {
register_info.push(Line::from(vec![Span::raw(
"CPU data temporarily unavailable (system busy)...",
)]));
}
Err(_) => {
register_info.push(Line::from(vec![Span::raw("Unable to access CPU data")]));
}
@ -578,9 +599,9 @@ impl ConsoleApp {
fn draw_ram_contents(&self, f: &mut Frame, area: Rect) {
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
// Try to get actual RAM data from the system
// Try to get actual RAM data from the system using try_lock
if self.config.show_ram {
match self.system.lock() {
match self.system.try_lock() {
Ok(system) => {
let ram_data = system.get_ram_data(self.config.max_ram_rows);
@ -611,6 +632,11 @@ impl ConsoleApp {
}
}
}
Err(std::sync::TryLockError::WouldBlock) => {
ram_info.push(Line::from(vec![Span::raw(
"RAM data temporarily unavailable (system busy)...",
)]));
}
Err(_) => {
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
}

View file

@ -206,15 +206,12 @@ fn main() {
"Console features: Real-time monitoring, formatted output, non-blocking operation"
);
// Start the emulator system in a separate thread
// This allows the console interface to run while emulation continues
let system_runner = system_arc.clone();
thread::spawn(move || {
if let Ok(mut system) = system_runner.lock() {
println!("DEBUG: Starting system for console mode");
system.run();
}
});
// Start the emulator system using start() which doesn't block
// This allows the console interface to access the system while emulation runs
if let Ok(mut system) = system_arc.lock() {
println!("DEBUG: Starting system for console mode (non-blocking)");
system.start();
}
// Launch console interface (blocks until console is closed)
if let Err(e) = run_console(system_arc, console_config) {
@ -229,15 +226,12 @@ fn main() {
println!("Starting graphical user interface...");
println!("GUI features: Visual monitoring, interactive controls, real-time updates");
// Start the emulator system in a separate thread
// Start the emulator system using start() which doesn't block
// This allows the GUI to remain responsive while emulation runs
let system_runner = system_arc.clone();
thread::spawn(move || {
if let Ok(mut system) = system_runner.lock() {
println!("DEBUG: Starting system for GUI mode");
system.run();
}
});
if let Ok(mut system) = system_arc.lock() {
println!("DEBUG: Starting system for GUI mode (non-blocking)");
system.start();
}
// Launch GUI application (blocks until GUI window is closed)
// The GUI will handle all user interactions and system monitoring

View file

@ -377,11 +377,15 @@ impl SystemFactory {
}
}
/// Thread handles for running components
type ComponentHandles = Vec<(String, std::thread::JoinHandle<()>)>;
/// A configurable system created from JSON configuration
pub struct ConfigurableSystem {
config: SystemConfig,
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
is_running: bool,
is_running: Arc<Mutex<bool>>,
component_handles: Arc<Mutex<Option<ComponentHandles>>>,
}
impl ConfigurableSystem {
@ -392,20 +396,35 @@ impl ConfigurableSystem {
ConfigurableSystem {
config,
components,
is_running: false,
is_running: Arc::new(Mutex::new(false)),
component_handles: Arc::new(Mutex::new(None)),
}
}
pub fn run(&mut self) {
self.is_running = true;
let mut handles = vec![];
/// Start the system without blocking.
///
/// This method spawns component threads and returns immediately,
/// allowing the caller to retain access to the system via the mutex.
/// The system will continue running in the background until `stop()` is called.
pub fn start(&mut self) {
let mut is_running = self.is_running.lock().unwrap();
if *is_running {
println!("System is already running");
return;
}
*is_running = true;
drop(is_running); // Release lock before spawning threads
println!("Starting configurable system: {}", self.config.name);
println!("Description: {}", self.config.description);
let mut handles = vec![];
let is_running_clone = Arc::clone(&self.is_running);
for (name, component) in &self.components {
let comp_clone = Arc::clone(component);
let name_clone = name.clone();
let running_clone = Arc::clone(&is_running_clone);
let handle = std::thread::spawn(move || {
println!("DEBUG: Starting component thread: {}", name_clone);
@ -414,17 +433,46 @@ impl ConfigurableSystem {
comp.run();
}
println!("DEBUG: Component {} stopped", name_clone);
// Signal that system should stop when any component stops
if let Ok(mut running) = running_clone.lock() {
*running = false;
}
});
handles.push((name.clone(), handle));
}
println!("All components started. System running...");
// Store handles for later cleanup
if let Ok(mut stored_handles) = self.component_handles.lock() {
*stored_handles = Some(handles);
}
println!("All components started. System running in background...");
}
/// Run the system (blocking version for backward compatibility).
///
/// This method starts the system and then monitors it until stopped.
/// Note: This method holds the mutex lock for the entire duration,
/// which can cause issues with console/GUI access. Use `start()` instead
/// for non-blocking operation.
pub fn run(&mut self) {
self.start();
// Monitor system state during execution
self.monitor_system_state();
self.is_running = false;
// Stop and cleanup
self.cleanup();
}
/// Clean up component threads and stop all components.
fn cleanup(&mut self) {
// Signal stop
if let Ok(mut is_running) = self.is_running.lock() {
*is_running = false;
}
// Stop all components
println!("\nStopping system components...");
@ -436,10 +484,14 @@ impl ConfigurableSystem {
}
// Wait for threads
for (name, handle) in handles {
match handle.join() {
Ok(_) => println!("Component {} thread finished", name),
Err(_) => eprintln!("Component {} thread panicked", name),
if let Ok(mut handles_guard) = self.component_handles.lock() {
if let Some(handles) = handles_guard.take() {
for (name, handle) in handles {
match handle.join() {
Ok(_) => println!("Component {} thread finished", name),
Err(_) => eprintln!("Component {} thread panicked", name),
}
}
}
}
@ -459,7 +511,11 @@ impl ConfigurableSystem {
std::thread::sleep(std::time::Duration::from_millis(500));
// Check if system should stop
if !self.is_running {
let should_continue = self.is_running.lock()
.map(|guard| *guard)
.unwrap_or(false);
if !should_continue {
println!("\nSystem stop requested - terminating monitoring");
break;
}
@ -555,11 +611,17 @@ impl ConfigurableSystem {
}
pub fn stop(&mut self) {
self.is_running = false;
if let Ok(mut is_running) = self.is_running.lock() {
*is_running = false;
}
// Also cleanup threads
self.cleanup();
}
pub fn is_running(&self) -> bool {
self.is_running
self.is_running.lock()
.map(|guard| *guard)
.unwrap_or(false)
}
/// Get access to components for monitoring purposes