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 {
|
while self.running {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
// Handle input
|
// Handle input with longer poll interval to reduce CPU usage
|
||||||
if let Ok(true) = event::poll(Duration::from_millis(10)) {
|
if let Ok(true) = event::poll(Duration::from_millis(50)) {
|
||||||
if let Ok(Event::Key(key)) = event::read() {
|
if let Ok(Event::Key(key)) = event::read() {
|
||||||
self.handle_key_event(key.code, key.modifiers);
|
self.handle_key_event(key.code, key.modifiers);
|
||||||
}
|
}
|
||||||
|
|
@ -158,20 +158,30 @@ impl ConsoleApp {
|
||||||
last_draw = now;
|
last_draw = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if system should still be running
|
// Check if system should still be running using try_lock to avoid blocking
|
||||||
if let Ok(system) = self.system.lock() {
|
match self.system.try_lock() {
|
||||||
if !system.is_running() && !self.show_help {
|
Ok(system) => {
|
||||||
// System stopped, show final state briefly
|
if !system.is_running() && !self.show_help {
|
||||||
if let Err(e) = terminal.draw(|f| self.draw_ui(f)) {
|
// System stopped, show final state briefly
|
||||||
eprintln!("DEBUG: Failed to draw final UI: {}", e);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay to prevent busy waiting
|
// Small delay to prevent busy waiting
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore terminal
|
// Restore terminal
|
||||||
|
|
@ -464,10 +474,11 @@ impl ConsoleApp {
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
// System information
|
// System information - use try_lock to avoid blocking
|
||||||
let system_info = match self.system.lock() {
|
let system_info = match self.system.try_lock() {
|
||||||
Ok(system) => match system.get_system_info() {
|
Ok(system) => {
|
||||||
info => vec![
|
let info = system.get_system_info();
|
||||||
|
vec![
|
||||||
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
|
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
|
||||||
Line::from(vec![Span::raw(format!(
|
Line::from(vec![Span::raw(format!(
|
||||||
"Description: {}",
|
"Description: {}",
|
||||||
|
|
@ -487,8 +498,13 @@ impl ConsoleApp {
|
||||||
info.ram_size
|
info.ram_size
|
||||||
))]),
|
))]),
|
||||||
Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]),
|
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(_) => {
|
Err(_) => {
|
||||||
vec![Line::from(vec![Span::raw(
|
vec![Line::from(vec![Span::raw(
|
||||||
"System information unavailable",
|
"System information unavailable",
|
||||||
|
|
@ -526,7 +542,7 @@ impl ConsoleApp {
|
||||||
fn get_cpu_register_info(&self) -> Vec<Line<'static>> {
|
fn get_cpu_register_info(&self) -> Vec<Line<'static>> {
|
||||||
let mut register_info = vec![Line::from(vec![Span::raw("CPU Registers:")])];
|
let mut register_info = vec![Line::from(vec![Span::raw("CPU Registers:")])];
|
||||||
|
|
||||||
match self.system.lock() {
|
match self.system.try_lock() {
|
||||||
Ok(system) => {
|
Ok(system) => {
|
||||||
// Try to find Intel 4004 CPU and get register data
|
// Try to find Intel 4004 CPU and get register data
|
||||||
let cpu_data = system.get_cpu_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")]));
|
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(_) => {
|
Err(_) => {
|
||||||
register_info.push(Line::from(vec![Span::raw("Unable to access CPU data")]));
|
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) {
|
fn draw_ram_contents(&self, f: &mut Frame, area: Rect) {
|
||||||
let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])];
|
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 {
|
if self.config.show_ram {
|
||||||
match self.system.lock() {
|
match self.system.try_lock() {
|
||||||
Ok(system) => {
|
Ok(system) => {
|
||||||
let ram_data = system.get_ram_data(self.config.max_ram_rows);
|
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(_) => {
|
Err(_) => {
|
||||||
ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")]));
|
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"
|
"Console features: Real-time monitoring, formatted output, non-blocking operation"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start the emulator system in a separate thread
|
// Start the emulator system using start() which doesn't block
|
||||||
// This allows the console interface to run while emulation continues
|
// This allows the console interface to access the system while emulation runs
|
||||||
let system_runner = system_arc.clone();
|
if let Ok(mut system) = system_arc.lock() {
|
||||||
thread::spawn(move || {
|
println!("DEBUG: Starting system for console mode (non-blocking)");
|
||||||
if let Ok(mut system) = system_runner.lock() {
|
system.start();
|
||||||
println!("DEBUG: Starting system for console mode");
|
}
|
||||||
system.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Launch console interface (blocks until console is closed)
|
// Launch console interface (blocks until console is closed)
|
||||||
if let Err(e) = run_console(system_arc, console_config) {
|
if let Err(e) = run_console(system_arc, console_config) {
|
||||||
|
|
@ -229,15 +226,12 @@ fn main() {
|
||||||
println!("Starting graphical user interface...");
|
println!("Starting graphical user interface...");
|
||||||
println!("GUI features: Visual monitoring, interactive controls, real-time updates");
|
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
|
// This allows the GUI to remain responsive while emulation runs
|
||||||
let system_runner = system_arc.clone();
|
if let Ok(mut system) = system_arc.lock() {
|
||||||
thread::spawn(move || {
|
println!("DEBUG: Starting system for GUI mode (non-blocking)");
|
||||||
if let Ok(mut system) = system_runner.lock() {
|
system.start();
|
||||||
println!("DEBUG: Starting system for GUI mode");
|
}
|
||||||
system.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Launch GUI application (blocks until GUI window is closed)
|
// Launch GUI application (blocks until GUI window is closed)
|
||||||
// The GUI will handle all user interactions and system monitoring
|
// 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
|
/// A configurable system created from JSON configuration
|
||||||
pub struct ConfigurableSystem {
|
pub struct ConfigurableSystem {
|
||||||
config: SystemConfig,
|
config: SystemConfig,
|
||||||
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
components: HashMap<String, Arc<Mutex<Box<dyn Component>>>>,
|
||||||
is_running: bool,
|
is_running: Arc<Mutex<bool>>,
|
||||||
|
component_handles: Arc<Mutex<Option<ComponentHandles>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableSystem {
|
impl ConfigurableSystem {
|
||||||
|
|
@ -392,20 +396,35 @@ impl ConfigurableSystem {
|
||||||
ConfigurableSystem {
|
ConfigurableSystem {
|
||||||
config,
|
config,
|
||||||
components,
|
components,
|
||||||
is_running: false,
|
is_running: Arc::new(Mutex::new(false)),
|
||||||
|
component_handles: Arc::new(Mutex::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
/// Start the system without blocking.
|
||||||
self.is_running = true;
|
///
|
||||||
let mut handles = vec![];
|
/// 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!("Starting configurable system: {}", self.config.name);
|
||||||
println!("Description: {}", self.config.description);
|
println!("Description: {}", self.config.description);
|
||||||
|
|
||||||
|
let mut handles = vec![];
|
||||||
|
let is_running_clone = Arc::clone(&self.is_running);
|
||||||
|
|
||||||
for (name, component) in &self.components {
|
for (name, component) in &self.components {
|
||||||
let comp_clone = Arc::clone(component);
|
let comp_clone = Arc::clone(component);
|
||||||
let name_clone = name.clone();
|
let name_clone = name.clone();
|
||||||
|
let running_clone = Arc::clone(&is_running_clone);
|
||||||
|
|
||||||
let handle = std::thread::spawn(move || {
|
let handle = std::thread::spawn(move || {
|
||||||
println!("DEBUG: Starting component thread: {}", name_clone);
|
println!("DEBUG: Starting component thread: {}", name_clone);
|
||||||
|
|
@ -414,17 +433,46 @@ impl ConfigurableSystem {
|
||||||
comp.run();
|
comp.run();
|
||||||
}
|
}
|
||||||
println!("DEBUG: Component {} stopped", name_clone);
|
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));
|
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
|
// Monitor system state during execution
|
||||||
self.monitor_system_state();
|
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
|
// Stop all components
|
||||||
println!("\nStopping system components...");
|
println!("\nStopping system components...");
|
||||||
|
|
@ -436,10 +484,14 @@ impl ConfigurableSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for threads
|
// Wait for threads
|
||||||
for (name, handle) in handles {
|
if let Ok(mut handles_guard) = self.component_handles.lock() {
|
||||||
match handle.join() {
|
if let Some(handles) = handles_guard.take() {
|
||||||
Ok(_) => println!("Component {} thread finished", name),
|
for (name, handle) in handles {
|
||||||
Err(_) => eprintln!("Component {} thread panicked", name),
|
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));
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
|
|
||||||
// Check if system should stop
|
// 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");
|
println!("\nSystem stop requested - terminating monitoring");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -555,11 +611,17 @@ impl ConfigurableSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self) {
|
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 {
|
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
|
/// Get access to components for monitoring purposes
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue