use super::channel_registry::ChannelRegistry;
use super::host_tools::HostToolRegistry;
use monoloop_contracts::TransactionLimits;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::watch;
#[derive(Debug)]
pub struct StoppedGate {
tx: watch::Sender<bool>,
rx: watch::Receiver<bool>,
}
impl Default for StoppedGate {
fn default() -> Self {
let (tx, rx) = watch::channel(false);
Self { tx, rx }
}
}
impl StoppedGate {
pub fn new() -> Self {
Self::default()
}
pub fn release(&self) {
let _ = self.tx.send(true);
}
pub async fn wait_released(&self) {
let mut rx = self.rx.clone();
let _ = rx.wait_for(|released| *released).await;
}
}
#[derive(Debug, Default)]
pub struct StartHoldGate {
held: AtomicBool,
}
impl StartHoldGate {
pub fn new() -> Self {
Self {
held: AtomicBool::new(false),
}
}
pub fn hold(&self) {
self.held.store(true, Ordering::SeqCst);
}
pub fn release(&self) {
self.held.store(false, Ordering::SeqCst);
}
pub fn is_held(&self) -> bool {
self.held.load(Ordering::SeqCst)
}
}
#[derive(Debug, Default)]
pub struct ControlHoldGate {
held: AtomicBool,
}
impl ControlHoldGate {
pub fn new() -> Self {
Self {
held: AtomicBool::new(false),
}
}
pub fn hold(&self) {
self.held.store(true, Ordering::SeqCst);
}
pub fn release(&self) {
self.held.store(false, Ordering::SeqCst);
}
pub fn is_held(&self) -> bool {
self.held.load(Ordering::SeqCst)
}
}
#[derive(Debug)]
pub struct FinalizerHoldGate {
released: AtomicBool,
notify: tokio::sync::Notify,
}
impl Default for FinalizerHoldGate {
fn default() -> Self {
Self {
released: AtomicBool::new(false),
notify: tokio::sync::Notify::new(),
}
}
}
impl FinalizerHoldGate {
pub fn new() -> Self {
Self::default()
}
pub fn release(&self) {
self.released.store(true, Ordering::SeqCst);
self.notify.notify_waiters();
}
pub async fn wait_released(&self) {
loop {
if self.released.load(Ordering::SeqCst) {
return;
}
let notified = self.notify.notified();
if self.released.load(Ordering::SeqCst) {
return;
}
notified.await;
}
}
}
#[derive(Debug)]
pub struct JoinOnlySpillInject {
entered: AtomicBool,
released: AtomicBool,
parked_thread: std::sync::Mutex<Option<std::thread::Thread>>,
}
impl Default for JoinOnlySpillInject {
fn default() -> Self {
Self::new()
}
}
impl JoinOnlySpillInject {
pub fn new() -> Self {
Self {
entered: AtomicBool::new(false),
released: AtomicBool::new(false),
parked_thread: std::sync::Mutex::new(None),
}
}
pub fn is_entered(&self) -> bool {
self.entered.load(Ordering::SeqCst)
}
pub(crate) fn is_released(&self) -> bool {
self.released.load(Ordering::SeqCst)
}
pub(crate) fn mark_entered(&self) {
self.entered.store(true, Ordering::SeqCst);
}
pub(crate) fn store_parked_thread(&self, thread: std::thread::Thread) {
*self.parked_thread.lock().unwrap_or_else(|e| e.into_inner()) = Some(thread);
}
pub fn release(&self) {
self.released.store(true, Ordering::SeqCst);
if let Some(thread) = self
.parked_thread
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
{
thread.unpark();
}
}
}
#[derive(Clone, Debug)]
pub struct RuntimeConfig {
pub transaction_limits: TransactionLimits,
pub enable_mcp_listener: bool,
pub default_shutdown_deadline: Duration,
pub block_stopped: Option<Arc<StoppedGate>>,
pub hold_start: Option<Arc<StartHoldGate>>,
pub hold_control: Option<Arc<ControlHoldGate>>,
pub start_queue_capacity: Option<usize>,
pub hold_finalizer_after_seal: Option<Arc<FinalizerHoldGate>>,
pub hold_executor_teardown: Option<Arc<StoppedGate>>,
pub inject_non_yielding_service: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
pub inject_join_only_spill: Option<Arc<JoinOnlySpillInject>>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
transaction_limits: TransactionLimits::default(),
enable_mcp_listener: false,
default_shutdown_deadline: Duration::from_secs(30),
block_stopped: None,
hold_start: None,
hold_control: None,
start_queue_capacity: None,
hold_finalizer_after_seal: None,
hold_executor_teardown: None,
inject_non_yielding_service: None,
inject_join_only_spill: None,
}
}
}
impl RuntimeConfig {
pub fn validate(&self) -> Result<(), super::StartupError> {
self.transaction_limits.validate().map_err(|e| match e {
monoloop_contracts::LimitsError::ZeroCapacity(f) => {
super::StartupError::InvalidConfig(f)
}
monoloop_contracts::LimitsError::Inconsistent(_) => {
super::StartupError::InvalidConfig("inconsistent transaction limits")
}
})?;
if self.default_shutdown_deadline.is_zero() {
return Err(super::StartupError::InvalidConfig(
"default_shutdown_deadline",
));
}
const MAX_TX_DEADLINE: std::time::Duration =
std::time::Duration::from_secs(365 * 24 * 3600);
if self.transaction_limits.transaction_deadline > MAX_TX_DEADLINE
|| std::time::Instant::now()
.checked_add(self.transaction_limits.transaction_deadline)
.is_none()
{
return Err(super::StartupError::InvalidConfig(
"transaction_deadline exceeds Instant-representable bound",
));
}
if let Some(cap) = self.start_queue_capacity {
if cap == 0 {
return Err(super::StartupError::InvalidConfig(
"start_queue_capacity must be nonzero when set",
));
}
}
Ok(())
}
}
pub struct RuntimeBootstrap {
pub config: RuntimeConfig,
pub channels: ChannelRegistry,
pub tools: HostToolRegistry,
}