use std::time::Duration;
use crate::types::{ControlChar, ProcessExitStatus, SessionState};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ShutdownStrategy {
Graceful,
Terminate,
Kill,
#[default]
Escalating,
}
#[derive(Debug, Clone)]
pub struct ShutdownConfig {
pub strategy: ShutdownStrategy,
pub graceful_timeout: Duration,
pub terminate_timeout: Duration,
pub exit_command: Option<String>,
pub wait_for_exit: bool,
}
impl Default for ShutdownConfig {
fn default() -> Self {
Self {
strategy: ShutdownStrategy::Escalating,
graceful_timeout: Duration::from_secs(5),
terminate_timeout: Duration::from_secs(3),
exit_command: Some("exit".to_string()),
wait_for_exit: true,
}
}
}
impl ShutdownConfig {
#[must_use]
pub fn graceful() -> Self {
Self {
strategy: ShutdownStrategy::Graceful,
..Default::default()
}
}
#[must_use]
pub fn kill() -> Self {
Self {
strategy: ShutdownStrategy::Kill,
wait_for_exit: false,
..Default::default()
}
}
#[must_use]
pub fn with_exit_command(mut self, command: impl Into<String>) -> Self {
self.exit_command = Some(command.into());
self
}
#[must_use]
pub const fn with_graceful_timeout(mut self, timeout: Duration) -> Self {
self.graceful_timeout = timeout;
self
}
}
#[derive(Debug, Clone)]
pub enum LifecycleEvent {
Started,
Ready,
StateChanged(SessionState),
ShuttingDown,
Closed,
Exited(ProcessExitStatus),
Error(String),
}
pub type LifecycleCallback = Box<dyn Fn(LifecycleEvent) + Send + Sync>;
pub struct LifecycleManager {
callbacks: Vec<LifecycleCallback>,
state: SessionState,
shutdown_config: ShutdownConfig,
}
impl LifecycleManager {
#[must_use]
pub fn new() -> Self {
Self {
callbacks: Vec::new(),
state: SessionState::Starting,
shutdown_config: ShutdownConfig::default(),
}
}
pub fn set_shutdown_config(&mut self, config: ShutdownConfig) {
self.shutdown_config = config;
}
#[must_use]
pub const fn shutdown_config(&self) -> &ShutdownConfig {
&self.shutdown_config
}
pub fn on_event(&mut self, callback: LifecycleCallback) {
self.callbacks.push(callback);
}
pub fn emit(&self, event: &LifecycleEvent) {
for callback in &self.callbacks {
callback(event.clone());
}
}
pub fn set_state(&mut self, state: SessionState) {
self.state = state;
self.emit(&LifecycleEvent::StateChanged(state));
}
#[must_use]
pub const fn state(&self) -> &SessionState {
&self.state
}
pub fn started(&mut self) {
self.set_state(SessionState::Running);
self.emit(&LifecycleEvent::Started);
}
pub fn ready(&mut self) {
self.emit(&LifecycleEvent::Ready);
}
pub fn shutting_down(&mut self) {
self.set_state(SessionState::Closing);
self.emit(&LifecycleEvent::ShuttingDown);
}
pub fn closed(&mut self) {
self.set_state(SessionState::Closed);
self.emit(&LifecycleEvent::Closed);
}
pub fn exited(&mut self, status: ProcessExitStatus) {
self.set_state(SessionState::Exited(status));
self.emit(&LifecycleEvent::Exited(status));
}
pub fn error(&mut self, message: impl Into<String>) {
self.emit(&LifecycleEvent::Error(message.into()));
}
}
impl Default for LifecycleManager {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for LifecycleManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LifecycleManager")
.field("state", &self.state)
.field("callbacks", &self.callbacks.len())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Signal {
Interrupt,
Quit,
Terminate,
Kill,
Hangup,
User1,
User2,
}
impl Signal {
#[must_use]
pub const fn as_control_char(&self) -> Option<ControlChar> {
match self {
Self::Interrupt => Some(ControlChar::CtrlC),
Self::Quit => Some(ControlChar::CtrlBackslash),
_ => None,
}
}
#[cfg(unix)]
#[must_use]
pub const fn as_signal_number(&self) -> i32 {
match self {
Self::Interrupt => 2, Self::Quit => 3, Self::Terminate => 15, Self::Kill => 9, Self::Hangup => 1, Self::User1 => 10, Self::User2 => 12, }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shutdown_config_default() {
let config = ShutdownConfig::default();
assert_eq!(config.strategy, ShutdownStrategy::Escalating);
assert!(config.exit_command.is_some());
}
#[test]
fn lifecycle_manager_state_transitions() {
let mut manager = LifecycleManager::new();
assert!(matches!(manager.state(), SessionState::Starting));
manager.started();
assert!(matches!(manager.state(), SessionState::Running));
manager.shutting_down();
assert!(matches!(manager.state(), SessionState::Closing));
manager.closed();
assert!(matches!(manager.state(), SessionState::Closed));
}
#[test]
fn signal_control_char() {
assert_eq!(
Signal::Interrupt.as_control_char(),
Some(ControlChar::CtrlC)
);
assert_eq!(Signal::Terminate.as_control_char(), None);
}
}