use crate::context::PluginContext;
use crate::error::PluginError;
use crate::forwarder::{Forwarder, make_forwarder};
use crate::lifecycle::{
EVENT_PLUG_DOWN, EVENT_PLUG_DOWN_EVENT_PREFIX, EVENT_PLUG_UP, EVENT_PLUG_UP_PREFIX,
};
use crate::plugin::{CLOSE_JOIN_TIMEOUT, Plugin, valid_plugin_name};
use crate::registry::{EntryStats, PluginEntry, PluginStateCell, PluginStateCode, Registry};
use crate::service::PlugNotice;
use crate::worker::spawn_worker;
use ek_event::{EventEngine, HandlerId};
use std::any::Any;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginState {
Ready,
Running,
Stopping,
Exited,
Quarantined,
}
impl PluginState {
pub(crate) fn from_code(c: u8) -> Self {
match c {
0 => Self::Ready,
1 => Self::Running,
2 => Self::Stopping,
3 => Self::Exited,
_ => Self::Quarantined,
}
}
fn from_code_enum(code: PluginStateCode) -> Self {
Self::from_code(code as u8)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginInfo {
pub name: String,
pub version: String,
pub state: PluginState,
pub deps: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MailStats {
pub spilled: u64,
pub panics: u64,
pub pending: u64,
pub peak: u64,
pub soft_cap: u64,
}
pub struct MainEngine {
event: Arc<EventEngine>,
registry: Arc<Registry>,
started_order: Mutex<Option<Vec<String>>>,
closed: RwLock<bool>,
}
impl MainEngine {
pub fn new(event: Option<Arc<EventEngine>>) -> Self {
Self {
event: event.unwrap_or_else(|| Arc::new(EventEngine::new())),
registry: Arc::new(Registry::new()),
started_order: Mutex::new(None),
closed: RwLock::new(false),
}
}
pub fn event_engine(&self) -> &Arc<EventEngine> {
&self.event
}
fn closed_check(&self) -> Result<(), PluginError> {
if *self.closed.read().unwrap() {
Err(PluginError::Stopped)
} else {
Ok(())
}
}
pub fn add_plugin(&self, plugin: Arc<impl Plugin + 'static>) -> Result<(), PluginError> {
self.closed_check()?;
let name = plugin.name().to_string();
if !valid_plugin_name(&name) {
return Err(PluginError::IllegalName(name));
}
let deps: Vec<String> = plugin.deps().iter().map(|d| d.to_string()).collect();
if deps.iter().any(|d| d == &name) {
return Err(PluginError::DepCycle(vec![name]));
}
let soft_cap = plugin.mailbox_soft_cap();
let any: Arc<dyn Any + Send + Sync> = Arc::clone(&plugin) as Arc<dyn Any + Send + Sync>;
let dyn_plugin: Arc<dyn Plugin> = plugin;
let svc_ids: Vec<String> = {
let mut ids = Vec::new();
for d in dyn_plugin.services() {
if !ids.contains(&d.id) {
ids.push(d.id);
}
}
ids
};
let mut w = self.registry.state_of.write().unwrap();
if w.entries.contains_key(&name) {
return Err(PluginError::Duplicate(name));
}
let entry = Arc::new(PluginEntry {
name: name.clone(),
plugin: dyn_plugin,
any,
state: PluginStateCell::new(PluginStateCode::Ready),
soft_cap,
deps,
mailbox: Mutex::new(None),
forwarder_ids: Mutex::new(Vec::new()),
worker: Mutex::new(None),
stats: Arc::new(EntryStats::default()),
});
w.order.push(entry.name.clone());
w.entries.insert(entry.name.clone(), entry);
drop(w);
self.registry.index_put(&name, &svc_ids);
self.registry.invalidate(&name);
let mut started = self.started_order.lock().unwrap();
if started.is_some() {
let entry = self.registry.get(&name).expect("just inserted");
self.launch_one(&entry);
started.as_mut().expect("checked Some").push(name);
}
Ok(())
}
pub fn get_state(&self, name: &str) -> Option<PluginState> {
self.registry.get(name).map(|e| e.state.get())
}
pub fn get_plugin(&self, name: &str) -> Option<PluginInfo> {
self.registry.info_of(name)
}
pub fn get_typed<T: Plugin + 'static>(&self, name: &str) -> Option<Arc<T>> {
let entry = self.registry.get(name)?;
Arc::downcast::<T>(Arc::clone(&entry.any)).ok()
}
#[must_use]
pub fn plugin_arc(&self, name: &str) -> Option<Arc<dyn Plugin>> {
let entry = self.registry.get(name)?;
Some(Arc::clone(&entry.plugin))
}
pub fn plugin_names(&self) -> Vec<String> {
self.registry.names()
}
pub fn mail_stats(&self, name: &str) -> Option<MailStats> {
self.registry.get(name).map(|e| MailStats {
spilled: e.stats.spilled.load(Ordering::Acquire),
panics: e.stats.panics.load(Ordering::Acquire),
pending: e.stats.pending.load(Ordering::Acquire),
peak: e.stats.peak.load(Ordering::Acquire),
soft_cap: e.soft_cap,
})
}
pub fn start(&self) -> Result<(), PluginError> {
let mut started = self.started_order.lock().unwrap();
if started.is_some() {
return Ok(());
}
self.closed_check()?;
self.event.start();
let order = self.registry.topo_order()?;
let mut launched = Vec::with_capacity(order.len());
for entry in order {
self.launch_one(&entry);
launched.push(entry.name.clone());
}
*started = Some(launched);
Ok(())
}
fn launch_one(&self, entry: &Arc<PluginEntry>) {
let ctx = Arc::new(PluginContext::new(
Arc::clone(&self.event),
Arc::clone(&self.registry),
entry.name.clone(),
));
entry.plugin.on_init(&ctx);
let (tx, rx) = crossbeam_channel::unbounded::<ek_event::Event>();
*entry.mailbox.lock().unwrap() = Some(tx.clone());
let handler = spawn_worker(
Arc::clone(&entry.plugin),
rx,
entry.state.clone(),
Arc::clone(&entry.stats),
Arc::clone(&self.event),
);
*entry.worker.lock().unwrap() = Some(handler);
let _ = entry
.state
.cas(PluginStateCode::Ready, PluginStateCode::Running);
let mut ids: Vec<HandlerId> = Vec::new();
for ty in entry.plugin.routes() {
let fwd = Forwarder::new(
tx.clone(),
Arc::clone(&entry.stats),
entry.soft_cap,
entry.name.clone(),
);
let id = self.event.register(ty.into_owned(), make_forwarder(fwd));
ids.push(id);
}
*entry.forwarder_ids.lock().unwrap() = ids;
entry.plugin.on_start();
let version = entry.plugin.version().into_owned();
let _ = self.event.put(ek_event::Event::with_data(
format!("{EVENT_PLUG_UP_PREFIX}{}", entry.name),
Arc::new(version.clone()),
));
let _ = self.event.put(ek_event::Event::with_data(
EVENT_PLUG_UP,
Arc::new(PlugNotice {
name: entry.name.clone(),
version,
}),
));
}
fn close_one(&self, entry: &Arc<PluginEntry>, deadline: Instant) {
let pre_state = entry.state.get();
let call_stop = pre_state != PluginState::Ready;
let _ = entry
.state
.cas(PluginStateCode::Running, PluginStateCode::Stopping);
let ids: Vec<HandlerId> = std::mem::take(&mut *entry.forwarder_ids.lock().unwrap());
for id in ids {
self.event.unregister(id);
}
if call_stop {
entry.plugin.on_stop();
}
*entry.mailbox.lock().unwrap() = None;
while !entry.stats.done.load(Ordering::Acquire) && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(2));
}
if let Some(handle) = entry.worker.lock().unwrap().take() {
if entry.stats.done.load(Ordering::Acquire) {
let _ = handle.join();
} else {
eprintln!(
"ek-core: close timeout: worker of {:?} still busy; detach and keep draining",
entry.name
);
}
}
let _ = entry
.state
.cas(PluginStateCode::Stopping, PluginStateCode::Exited);
}
pub fn close(&self, timeout: Duration) {
let order = self.started_order.lock().unwrap().take();
*self.closed.write().unwrap() = true;
let deadline = Instant::now() + timeout.max(Duration::from_millis(1));
if let Some(order) = order {
for name in order.iter().rev() {
if let Some(entry) = self.registry.get(name) {
self.close_one(&entry, deadline);
}
}
}
self.registry.clear();
}
pub fn close_all(&self, timeout: Duration) {
self.close(timeout);
self.event.stop();
}
pub fn remove_plugin(&self, name: &str) -> Result<(), PluginError> {
self.closed_check()?;
let entry = self
.registry
.get(name)
.ok_or_else(|| PluginError::UnknownName(name.to_string()))?;
let dependents = self.registry.any_depends_on(name);
if !dependents.is_empty() {
eprintln!(
"ek-core: remove_plugin {:?} still depended on by {:?}",
name, dependents
);
}
let deadline = Instant::now() + CLOSE_JOIN_TIMEOUT;
if entry.state.get() == PluginState::from_code_enum(PluginStateCode::Running) {
self.close_one(&entry, deadline);
} else {
let ids: Vec<HandlerId> = std::mem::take(&mut *entry.forwarder_ids.lock().unwrap());
for id in ids {
self.event.unregister(id);
}
*entry.mailbox.lock().unwrap() = None;
}
let version = entry.plugin.version().into_owned();
self.registry
.remove(name)
.ok_or_else(|| PluginError::UnknownName(name.to_string()))?;
self.registry.index_remove(name);
self.registry.invalidate(name);
let _ = self.event.put(ek_event::Event::with_data(
format!("{EVENT_PLUG_DOWN_EVENT_PREFIX}{name}"),
Arc::new(version.clone()),
));
let _ = self.event.put(ek_event::Event::with_data(
EVENT_PLUG_DOWN,
Arc::new(PlugNotice {
name: name.to_string(),
version,
}),
));
Ok(())
}
}