// ABOUTME: Plugin lifecycle management system for deterministic initialization and health monitoring
// ABOUTME: Provides traits and managers for consistent plugin startup, health checks, and graceful shutdown
//
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 Pierre Fitness Intelligence
//! Plugin Lifecycle Management
//!
//! This module provides a deterministic plugin initialization system with:
//! - Explicit initialization order
//! - Health check monitoring
//! - Graceful degradation
//! - Proper shutdown hooks
/// Core system plugin adapters (database, cache, auth)
pub mod plugins;
use crate::errors::{AppError, AppResult};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tokio::time::{error::Elapsed, timeout};
use tracing::{error, info, warn};
/// Plugin lifecycle state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PluginState {
/// Plugin is not yet initialized
Uninitialized,
/// Plugin is currently initializing
Initializing,
/// Plugin is ready and operational
Ready,
/// Plugin initialization failed
Failed,
/// Plugin is shutting down
ShuttingDown,
/// Plugin has shut down
Shutdown,
}
/// Plugin health status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginHealth {
/// Plugin name
pub name: String,
/// Current state
pub state: PluginState,
/// Health check status
pub healthy: bool,
/// Optional status message
pub message: Option<String>,
/// Last health check timestamp
pub last_check: chrono::DateTime<chrono::Utc>,
}
/// Plugin trait for lifecycle management
#[async_trait]
pub trait Plugin: Send + Sync {
/// Get plugin name
fn name(&self) -> &str;
/// Get plugin initialization priority (lower = earlier, 0-100)
fn priority(&self) -> u8 {
50 // Default medium priority
}
/// Initialize the plugin
///
/// # Errors
/// Returns an error if initialization fails
async fn initialize(&mut self) -> AppResult<()>;
/// Perform health check
///
/// # Errors
/// Returns an error if health check fails
async fn health_check(&self) -> AppResult<PluginHealth>;
/// Gracefully shutdown the plugin
///
/// # Errors
/// Returns an error if shutdown fails
async fn shutdown(&mut self) -> AppResult<()>;
/// Get current plugin state
fn state(&self) -> PluginState;
/// Check if plugin is required for server operation
fn is_required(&self) -> bool {
true // Default: all plugins are required
}
}
/// Plugin manager for orchestrating initialization and health checks
pub struct PluginManager {
plugins: Vec<Box<dyn Plugin>>,
initialization_timeout: Duration,
}
impl PluginManager {
/// Create a new plugin manager
#[must_use]
pub fn new() -> Self {
Self {
plugins: Vec::new(),
initialization_timeout: Duration::from_secs(30),
}
}
/// Register a plugin
pub fn register(&mut self, plugin: Box<dyn Plugin>) {
info!("Registering plugin: {}", plugin.name());
self.plugins.push(plugin);
}
/// Handle plugin initialization result and log appropriately
fn handle_plugin_init(
result: Result<Result<(), AppError>, Elapsed>,
plugin_name: &str,
is_required: bool,
timeout: Duration,
) -> Result<(), AppError> {
match result {
Ok(Ok(())) => {
info!("Plugin '{}' initialized successfully", plugin_name);
Ok(())
}
Ok(Err(e)) => Self::handle_init_error(e, plugin_name, is_required),
Err(_) => Self::handle_init_timeout(plugin_name, is_required, timeout),
}
}
fn handle_init_error(
e: AppError,
plugin_name: &str,
is_required: bool,
) -> Result<(), AppError> {
if is_required {
error!(
"Required plugin '{}' failed to initialize: {}",
plugin_name, e
);
Err(e)
} else {
warn!(
"Optional plugin '{}' failed to initialize: {}",
plugin_name, e
);
Ok(())
}
}
fn handle_init_timeout(
plugin_name: &str,
is_required: bool,
timeout: Duration,
) -> Result<(), AppError> {
if is_required {
error!(
"Required plugin '{}' initialization timed out after {:?}",
plugin_name, timeout
);
Err(AppError::internal(format!(
"Plugin initialization timeout: {plugin_name}"
)))
} else {
warn!("Optional plugin '{}' initialization timed out", plugin_name);
Ok(())
}
}
/// Initialize all plugins in priority order
///
/// # Errors
/// Returns an error if any required plugin fails to initialize
pub async fn initialize_all(&mut self) -> AppResult<()> {
info!("Initializing {} plugins", self.plugins.len());
self.plugins.sort_by_key(|p| p.priority());
let init_timeout = self.initialization_timeout;
for plugin in &mut self.plugins {
let plugin_name = plugin.name().to_owned();
let is_required = plugin.is_required();
let priority = plugin.priority();
info!(
"Initializing plugin '{}' (priority: {}, required: {})",
plugin_name, priority, is_required
);
let result = timeout(init_timeout, plugin.initialize()).await;
Self::handle_plugin_init(result, &plugin_name, is_required, init_timeout)?;
}
info!("All plugins initialized successfully");
Ok(())
}
/// Perform health checks on all plugins
#[must_use]
pub async fn health_check_all(&self) -> Vec<PluginHealth> {
let mut results = Vec::new();
for plugin in &self.plugins {
match plugin.health_check().await {
Ok(health) => results.push(health),
Err(e) => {
error!("Health check failed for plugin '{}': {}", plugin.name(), e);
results.push(PluginHealth {
name: plugin.name().to_owned(),
state: plugin.state(),
healthy: false,
message: Some(format!("Health check error: {e}")),
last_check: chrono::Utc::now(),
});
}
}
}
results
}
/// Shutdown all plugins in reverse priority order
///
/// # Errors
/// Returns an error if any plugin fails to shutdown gracefully
pub async fn shutdown_all(&mut self) -> AppResult<()> {
info!("Shutting down {} plugins", self.plugins.len());
// Reverse order for shutdown
self.plugins.reverse();
for plugin in &mut self.plugins {
Self::shutdown_plugin(plugin).await;
}
info!("All plugins shut down");
Ok(())
}
async fn shutdown_plugin(plugin: &mut Box<dyn Plugin>) {
let plugin_name = plugin.name().to_owned();
info!("Shutting down plugin '{}'", plugin_name);
if let Err(e) = plugin.shutdown().await {
error!("Plugin '{}' shutdown error: {}", plugin_name, e);
// Continue shutting down other plugins even if one fails
}
}
/// Get overall system health status
#[must_use]
pub async fn is_healthy(&self) -> bool {
let health_checks = self.health_check_all().await;
// Check if all required plugins are healthy
for health in &health_checks {
let plugin = self.plugins.iter().find(|p| p.name() == health.name);
if let Some(p) = plugin {
if p.is_required() && !health.healthy {
return false;
}
}
}
true
}
}
impl Default for PluginManager {
fn default() -> Self {
Self::new()
}
}