fix: resolve mutex deadlock in console/GUI mode
This commit is contained in:
parent
9d328ee87b
commit
d1cc176cc7
3 changed files with 132 additions and 50 deletions
|
|
@ -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")]));
|
||||
}
|
||||
|
|
|
|||
28
src/main.rs
28
src/main.rs
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue