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

@ -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