use anyhow::Context;
use anyhow::Result;
use crankshaft_config::backend::Config;
use crankshaft_events::Event;
use indexmap::IndexMap;
use tokio::sync::broadcast;
use tokio_util::sync::CancellationToken;
use tracing::debug;
pub mod service;
pub mod task;
pub use task::Task;
use crate::service::Runner;
use crate::service::runner::Backend;
use crate::service::runner::TaskHandle;
const EVENTS_CHANNEL_CAPACITY: usize = 100;
#[derive(Debug)]
pub struct Engine {
runners: IndexMap<String, Runner>,
events: Option<broadcast::Sender<Event>>,
#[cfg(feature = "monitoring")]
monitor: Option<crankshaft_monitor::Monitor>,
}
impl Engine {
pub fn new() -> Self {
let (events_tx, _) = broadcast::channel(EVENTS_CHANNEL_CAPACITY);
Self {
runners: Default::default(),
events: Some(events_tx),
#[cfg(feature = "monitoring")]
monitor: None,
}
}
#[cfg(feature = "monitoring")]
pub async fn new_with_monitoring(addr: std::net::SocketAddr) -> Self {
let (events_tx, _) = broadcast::channel(EVENTS_CHANNEL_CAPACITY);
let monitor = crankshaft_monitor::Monitor::start(addr, events_tx.clone()).await;
Self {
runners: Default::default(),
events: Some(events_tx),
monitor: Some(monitor),
}
}
pub async fn with(mut self, config: Config) -> Result<Self> {
let (name, kind, max_tasks, defaults) = config.into_parts();
let runner = Runner::initialize(kind, max_tasks, defaults, self.events.clone()).await?;
self.runners.insert(name, runner);
Ok(self)
}
pub fn subscribe(&self) -> Result<broadcast::Receiver<Event>> {
Ok(self
.events
.as_ref()
.context("engine has shut down")?
.subscribe())
}
pub fn runners(&self) -> impl Iterator<Item = &str> {
self.runners.keys().map(|key| key.as_ref())
}
pub async fn shutdown(mut self) {
self.events.take();
#[cfg(feature = "monitoring")]
if let Some(monitor) = self.monitor.take() {
monitor.stop().await;
}
}
pub async fn spawn(
&self,
name: impl AsRef<str>,
task: Task,
token: CancellationToken,
) -> Result<TaskHandle> {
let name = name.as_ref();
let backend = self
.runners
.get(name)
.unwrap_or_else(|| panic!("backend not found: {name}"));
debug!(
"submitting job{job} to the `{name}` backend",
job = task
.name
.as_ref()
.map(|name| format!(" with name `{name}`"))
.unwrap_or_default(),
);
backend.spawn(task, token).await
}
#[cfg(tokio_unstable)]
pub async fn start_instrument(delay_ms: u64) {
use tokio_metrics::RuntimeMonitor;
use tracing::info;
let handle = tokio::runtime::Handle::current();
let monitor = RuntimeMonitor::new(&handle);
tokio::spawn(async move {
for interval in monitor.intervals() {
info!("{:?}", interval.total_park_count);
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
}
});
}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}
impl Drop for Engine {
fn drop(&mut self) {
self.events.take();
}
}