#![doc = include_str!("../README.md")]
mod checkout;
mod pool;
#[cfg(feature = "telemetry")]
pub mod telemetry;
#[cfg(feature = "telemetry")]
pub use telemetry as telemetry_adapter;
mod worker;
use std::{borrow::Cow, error, fmt, io, num::NonZero, path::PathBuf, process::ExitStatus, thread, time::Duration};
pub use monty_proto::DEFAULT_PRINT_FLUSH_INTERVAL;
use monty_types::MontyException;
#[cfg(feature = "telemetry")]
use crate::telemetry::Metrics;
pub use crate::{
checkout::{
Checkout, CheckoutOptions, MountSpec, MountSpecMode, OnPrint, OnRawEvent, Persistence, PrintFuture, ReplConfig,
ResumeValue, TurnEvent, on_print_sync,
},
pool::Pool,
};
#[derive(Debug, Clone)]
pub enum MontyTransport {
Subprocess(PathBuf),
Websocket(String),
}
impl MontyTransport {
pub(crate) fn is_websocket(&self) -> bool {
matches!(self, Self::Websocket(_))
}
}
pub const DEFAULT_DURATION_LIMIT_GRACE: Duration = Duration::from_secs(1);
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub min_processes: usize,
pub max_processes: usize,
pub transport: MontyTransport,
pub checkout_timeout: Option<Duration>,
pub request_timeout: Option<Duration>,
pub feed_duration_limit_grace: Option<Duration>,
pub turn_duration_limit_grace: Option<Duration>,
pub max_checkouts_per_worker: Option<u32>,
pub auto_resume: bool,
#[cfg(feature = "telemetry")]
pub metrics: Option<Metrics>,
}
impl PoolConfig {
pub fn subprocess(binary_path: impl Into<PathBuf>) -> Self {
Self::with_transport(MontyTransport::Subprocess(binary_path.into()))
}
pub fn websocket(url: impl Into<String>) -> Self {
let mut config = Self::with_transport(MontyTransport::Websocket(url.into()));
config.min_processes = 0;
config
}
fn with_transport(transport: MontyTransport) -> Self {
Self {
min_processes: 1,
max_processes: thread::available_parallelism().map_or(4, NonZero::get),
transport,
checkout_timeout: None,
request_timeout: None,
feed_duration_limit_grace: Some(DEFAULT_DURATION_LIMIT_GRACE),
turn_duration_limit_grace: Some(DEFAULT_DURATION_LIMIT_GRACE),
max_checkouts_per_worker: None,
auto_resume: true,
#[cfg(feature = "telemetry")]
metrics: None,
}
}
}
#[derive(Debug)]
pub enum PoolError {
Crashed {
status: Option<ExitStatus>,
cause: CrashCause,
},
Timeout {
timeout: Duration,
},
Protocol(Cow<'static, str>),
Runtime(MontyException),
Typing(String),
Exhausted,
Spawn(String),
Finished,
Disconnected {
context: String,
},
Shutdown {
dump: Option<Vec<u8>>,
},
}
#[derive(Debug)]
pub enum CrashCause {
Vanished {
context: String,
},
Announced {
reason: String,
},
}
impl fmt::Display for PoolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Crashed { status, cause } => {
match cause {
CrashCause::Announced { reason } => write!(f, "monty worker crashed: {reason}")?,
CrashCause::Vanished { context } => write!(f, "monty worker crashed while {context}")?,
}
match status {
Some(status) => write!(f, " ({status})"),
None => Ok(()),
}
}
Self::Timeout { timeout } => {
write!(f, "monty worker killed after exceeding request timeout of {timeout:?}")
}
Self::Protocol(msg) => write!(f, "monty worker protocol error: {msg}"),
Self::Runtime(exc) => write!(f, "{exc}"),
Self::Typing(diagnostics) => write!(f, "type checking failed:\n{diagnostics}"),
Self::Exhausted => f.write_str("no monty worker became available within the checkout timeout"),
Self::Spawn(msg) => write!(f, "failed to spawn monty worker: {msg}"),
Self::Finished => f.write_str("this checkout has already been finished"),
Self::Disconnected { context } => write!(f, "monty worker connection closed while {context}"),
Self::Shutdown { dump } => match dump {
Some(_) => {
f.write_str("monty server is shutting down; the request did not run (restorable state attached)")
}
None => f.write_str("monty server is shutting down; the request did not run"),
},
}
}
}
impl error::Error for PoolError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::Runtime(exc) => Some(exc),
_ => None,
}
}
}
impl From<io::Error> for PoolError {
fn from(err: io::Error) -> Self {
Self::Crashed {
status: None,
cause: CrashCause::Vanished {
context: format!("performing I/O: {err}"),
},
}
}
}