use std::sync::{Arc, Mutex};
use anyhow::{Context, Result};
use async_trait::async_trait;
use tokio::task::JoinHandle;
use tracing::{error, info, warn};
use crate::context::PluginContext;
use crate::plugin::Plugin;
use crate::rpc::RpcMethodDef;
#[async_trait]
pub trait ServerPlugin: Send + Sync + 'static {
fn name(&self) -> &'static str;
async fn run(&self, ctx: PluginContext) -> Result<()>;
fn rpc_methods(&self) -> Vec<RpcMethodDef> {
Vec::new()
}
}
pub struct ServerPluginWrapper<S: ServerPlugin> {
inner: Arc<S>,
handle: Mutex<Option<JoinHandle<Result<()>>>>,
}
impl<S: ServerPlugin> ServerPluginWrapper<S> {
pub fn new(inner: S) -> Self {
Self {
inner: Arc::new(inner),
handle: Mutex::new(None),
}
}
}
#[async_trait]
impl<S: ServerPlugin> Plugin for ServerPluginWrapper<S> {
fn name(&self) -> &'static str {
self.inner.name()
}
async fn boot(&mut self, ctx: PluginContext) -> Result<()> {
let inner = self.inner.clone();
let handle = tokio::spawn(async move { inner.run(ctx).await });
*self.handle.lock().expect("mutex") = Some(handle);
info!(plugin = self.inner.name(), "server plugin booted");
Ok(())
}
async fn shutdown(&self) -> Result<()> {
let handle = {
let mut slot = self
.handle
.lock()
.map_err(|_| anyhow::anyhow!("ServerPluginWrapper handle mutex poisoned"))?;
slot.take()
};
let Some(handle) = handle else {
warn!(
plugin = self.inner.name(),
"shutdown called but no handle present"
);
return Ok(());
};
match handle.await {
Ok(Ok(())) => {
info!(plugin = self.inner.name(), "run loop completed cleanly");
Ok(())
}
Ok(Err(err)) => {
error!(plugin = self.inner.name(), error = ?err, "run loop returned error");
Err(err).context("ServerPlugin run loop failed")
}
Err(join_err) => {
error!(plugin = self.inner.name(), error = %join_err, "run loop join failed");
Err(anyhow::anyhow!("ServerPlugin task join failed: {join_err}"))
}
}
}
fn rpc_methods(&self) -> Vec<RpcMethodDef> {
self.inner.rpc_methods()
}
}