Skip to main content

folk_core/
plugin_registry.rs

1//! Plugin registry — owns the boot/shutdown ordering.
2
3use anyhow::{Context, Result};
4use folk_api::{Plugin, PluginContext};
5use tracing::{error, info};
6
7/// Holds plugins and orchestrates their boot/shutdown.
8///
9/// Boot order: registration order. Shutdown order: reverse.
10pub struct PluginRegistry {
11    plugins: Vec<Box<dyn Plugin>>,
12}
13
14impl PluginRegistry {
15    pub fn new() -> Self {
16        Self {
17            plugins: Vec::new(),
18        }
19    }
20
21    /// Register a plugin. Plugins are booted in registration order.
22    pub fn register(&mut self, plugin: Box<dyn Plugin>) {
23        info!(plugin = plugin.name(), "registered");
24        self.plugins.push(plugin);
25    }
26
27    /// Boot all plugins in order. If any boot returns `Err`, shutdown is
28    /// run on already-booted plugins (in reverse) and the error is propagated.
29    pub async fn boot_all(&mut self, ctx: &PluginContext) -> Result<()> {
30        for (booted_count, plugin) in self.plugins.iter_mut().enumerate() {
31            let name = plugin.name();
32            info!(plugin = name, "booting");
33            if let Err(e) = plugin.boot(ctx.clone()).await {
34                error!(plugin = name, error = ?e, "boot failed; rolling back");
35                self.shutdown_partial(booted_count).await;
36                return Err(e).with_context(|| format!("plugin '{name}' boot failed"));
37            }
38        }
39        Ok(())
40    }
41
42    /// Shut down all plugins in reverse order. Errors are logged but do not
43    /// stop the shutdown loop.
44    pub async fn shutdown_all(&self) {
45        self.shutdown_partial(self.plugins.len()).await;
46    }
47
48    async fn shutdown_partial(&self, count: usize) {
49        for plugin in self.plugins.iter().take(count).rev() {
50            let name = plugin.name();
51            info!(plugin = name, "shutting down");
52            if let Err(e) = plugin.shutdown().await {
53                error!(plugin = name, error = ?e, "shutdown error (continuing)");
54            }
55        }
56    }
57
58    pub fn names(&self) -> Vec<&'static str> {
59        self.plugins.iter().map(|p| p.name()).collect()
60    }
61}
62
63impl Default for PluginRegistry {
64    fn default() -> Self {
65        Self::new()
66    }
67}