use crate::context::PluginContext;
use crate::error::PluginError;
use crate::forwarder::{make_forwarder, Forwarder};
use crate::log_plugin::{LogData, EVENT_LOG, LOG_LEVEL_WARN};
use crate::plugin::{CLOSE_JOIN_TIMEOUT, Plugin, valid_plugin_name};
use crate::registry::{EntryStats, PluginEntry, PluginStateCell, PluginStateCode, Registry};
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 state: PluginState,
pub deps: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MailStats {
pub spilled: u64,
pub panics: u64,
}
fn log_event(source: &str, msg: String) -> ek_event::Event {
ek_event::Event::with_data(
EVENT_LOG,
Arc::new(LogData {
level: LOG_LEVEL_WARN,
source: source.to_string(),
msg,
}),
)
}
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(())
}
}
fn host_log(&self, msg: String) {
let _ = self.event.put(log_event("ek-core", msg));
}
pub fn add_plugin(&self, plugin: Arc<impl Plugin + 'static>) -> Result<(), PluginError> {
self.closed_check()?;
if self.started_order.lock().unwrap().is_some() {
return Err(PluginError::HostStarted);
}
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 mailbox_cap = plugin.mailbox_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 mut w = self.registry.state_of.write().unwrap();
if w.entries.contains_key(&name) {
return Err(PluginError::Duplicate(name));
}
for d in &deps {
if !w.entries.contains_key(d) {
return Err(PluginError::UnknownDep(d.clone()));
}
}
let entry = Arc::new(PluginEntry {
name: name.clone(),
plugin: dyn_plugin,
any,
state: PluginStateCell::new(PluginStateCode::Ready),
mailbox_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);
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.get(name).map(|e| PluginInfo {
name: e.name.clone(),
state: e.state.get(),
deps: e.deps.clone(),
})
}
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()
}
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),
})
}
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 {
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::bounded::<ek_event::Event>(entry.mailbox_cap);
*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(&ctx),
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),
Arc::clone(&self.event),
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();
launched.push(entry.name.clone());
}
*started = Some(launched);
Ok(())
}
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 {
self.host_log(format!(
"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() {
self.host_log(format!(
"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;
}
self.registry
.remove(name)
.ok_or_else(|| PluginError::UnknownName(name.to_string()))?;
Ok(())
}
}