Integrates signal handling with lifecycle

Refactors the application shutdown mechanism to use lifecycle commands.
OS signals (SIGTERM/SIGINT) now sends a `Dying` command directly to the brain.
The brain processes this command, transitioning through `Dying` to `Buried` state.
The main loop now explicitly waits for the brain to report a `Dead` or `Buried` state before exiting, ensuring a graceful shutdown.
Increases the brain's rest duration and adds debug logging.
This commit is contained in:
Russell Gilbert 2026-03-03 09:37:41 +00:00
parent 500b26703e
commit 58e1197ff4
3 changed files with 41 additions and 15 deletions

View file

@ -1 +1 @@
136 140

View file

@ -1,7 +1,7 @@
use tracing::info; use tracing::{debug, info};
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleCommandResponse, LifecycleReceipt}; use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleCommandResponse, LifecycleReceipt};
use crate::lifecycle::LifeState::{Buried, Genisys}; use crate::lifecycle::LifeState::{Dying, Buried, Genisys};
pub struct Brain { pub struct Brain {
state:LifeState, state:LifeState,
@ -36,14 +36,22 @@ impl Brain {
} }
fn rest() { fn rest() {
std::thread::sleep(std::time::Duration::from_millis(1)) debug!("Brain is resting.");
std::thread::sleep(std::time::Duration::from_millis(100))
} }
fn handle_divine_command(&mut self, command: LifecycleCommand) { fn handle_divine_command(&mut self, command: LifecycleCommand) {
info!("God has commanded {:?}", command); info!("God has commanded {:?}", command);
if self.can_transition_lifecycle(&command) { if self.can_transition_lifecycle(&command) {
self.set_lifecycle_state(&command); if command.required_state == Dying
{
self.set_lifecycle_state(&command, Buried);
} else {
self.set_lifecycle_state(&command, command.required_state);
}
return; return;
} }
@ -60,7 +68,7 @@ impl Brain {
fn can_transition_lifecycle(&self, command: &LifecycleCommand) -> bool fn can_transition_lifecycle(&self, command: &LifecycleCommand) -> bool
{ {
if (command.required_state == Buried) { if command.required_state == Buried || command.required_state == Dying {
return true return true
} }
@ -70,17 +78,19 @@ impl Brain {
false false
} }
fn set_lifecycle_state(&mut self, command: &LifecycleCommand) { fn set_lifecycle_state(&mut self, command: &LifecycleCommand, new_state: LifeState) {
self.state = command.required_state; self.state = new_state;
self.report_to_god(LifecycleReceipt{ self.report_to_god(LifecycleReceipt{
command: command.clone(), command: command.clone(),
response: LifecycleCommandResponse::Ok, response: LifecycleCommandResponse::Ok,
new_state: self.state new_state: self.state
}); });
} }
fn report_to_god(&self, receipt: LifecycleReceipt) { fn report_to_god(&self, receipt: LifecycleReceipt) {
info!("Reporting to God: Status = {:?}, NewState={:?}", receipt.response, receipt.new_state); info!("Reporting to God: Status = {:?}, NewState={:?}", receipt.response, receipt.new_state);
let _ = self.divine_tx.send(receipt); let _ = self.divine_tx.send(receipt);
} }
} }

View file

@ -1,27 +1,40 @@
pub mod lifecycle; pub mod lifecycle;
pub mod brain; pub mod brain;
use tracing::info; use std::fmt::Alignment::Left;
use tracing::{info, debug};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tracing_subscriber::registry;
use crate::brain::Brain; use crate::brain::Brain;
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleReceipt}; use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleReceipt};
fn main() { fn main() {
let shutdown_requested = Arc::new(AtomicBool::new(false));
setup_logging(); setup_logging();
info!("RustyLee: {}", env!("FULL_VERSION")); info!("RustyLee: {}", env!("FULL_VERSION"));
setup_os_signal_handler(shutdown_requested.clone());
let (tx, rx) = spawn_brain(); let (tx, rx) = spawn_brain();
setup_os_signal_handler(tx.clone());
let_there_be_life(&tx, &rx); let_there_be_life(&tx, &rx);
wait_for_death(tx, rx);
info!("God is watching.");
loop{
if let Ok(receipt) = rx.recv() {
debug!("Brain status: {:?}", receipt.new_state);
if receipt.new_state == LifeState::Dead || receipt.new_state == LifeState::Buried {
break;
} else {
thread::sleep(Duration::from_millis(100));
}
}
}
// wait_for_death(tx, rx);
info!("God: Brain has been buried. Shutting down."); info!("God: Brain has been buried. Shutting down.");
} }
@ -69,11 +82,14 @@ fn spawn_brain() -> (Sender<LifecycleCommand>, Receiver<LifecycleReceipt>) {
(to_brain_tx, from_brain_rx) (to_brain_tx, from_brain_rx)
} }
fn setup_os_signal_handler(s: Arc<AtomicBool>) { fn setup_os_signal_handler(tx: Sender<LifecycleCommand>) {
info!("Setting up SIGTERM/SIGINT handling."); info!("Setting up SIGTERM/SIGINT handling.");
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
info!("[Signal] Shutdown signal caught!"); info!("[Signal] Shutdown signal caught!");
s.store(true, Ordering::SeqCst); let _ = tx.send(LifecycleCommand {
required_state: LifeState::Dying,
command_time: Instant::now(),
});
}).expect("Error setting signal handler."); }).expect("Error setting signal handler.");
} }