saddle-runtime 0.3.26

Saddle managed asynchronous runtime and lifecycle
Documentation
//! R0 type-only storage descriptions. No allocation, task, or reservation issuer.
use super::*;
use std::alloc::{Layout, LayoutError};

#[derive(Debug, Clone, Copy)]
pub struct SharedLayout {
    pub payload: Layout,
    pub allocation: Layout,
}
pub fn shared(payload: Layout) -> Result<SharedLayout, LayoutError> {
    Ok(SharedLayout {
        payload,
        allocation: Layout::new::<[std::sync::atomic::AtomicUsize; 2]>()
            .extend(payload)?
            .0
            .pad_to_align(),
    })
}

/// The four original supervision fault roles, now lightweight, not four copies
/// of legacy diagnostics. This is a dormant shape: no constructor or activation.
#[allow(dead_code)]
struct RootState {
    publisher: Option<saddle_core::request_context::RequestRootPublisher>,
    current: Option<saddle_core::request_context::RequestExecutionView>,
    output: Option<EmergencyDiagnosticHandle>,
    id: Option<tokio::task::Id>,
    panic: Option<saddle_observability::root_diagnostic::RootRequestFailure>,
    cancelled: Option<saddle_observability::root_diagnostic::RootRequestFailure>,
    cleanup: Option<saddle_observability::root_diagnostic::RootRequestFailure>,
    control: Option<saddle_observability::root_diagnostic::RootRequestFailure>,
    finished: bool,
}

#[allow(dead_code)]
struct RootOutcome<T> {
    result: saddle_observability::root_diagnostic::RootSupervisionReturn<T>,
    state: Arc<Mutex<RootState>>,
    join_error: Option<tokio::task::JoinError>,
}

#[derive(Debug)]
pub struct TaskLayouts {
    /// Box holding pair_body: includes factory Option, not a second factory box.
    pub pair_box: Layout,
    /// Actual inner future allocation: owned_body for normal; SC body for reject.
    pub inner_box: Layout,
    /// Header moved into Tokio's stage, NOT another allocation.
    pub future: Layout,
    /// Actual output moved into Tokio's stage, NOT a simultaneous second cell.
    pub outcome: Layout,
    /// Actual Tokio Result<Outcome>; Stage adds its own repr(C) discriminant.
    pub join_result: Layout,
    pub legacy_state: SharedLayout,
    /// Replacement, not added to legacy_state. Neither shape is activated here.
    pub root_state: SharedLayout,
    pub root_outcome: Layout,
    pub root_join_result: Layout,
}

fn output<A, R>(_: impl FnOnce(A) -> R) -> Layout {
    Layout::new::<R>()
}

/// `constructor` is a type witness only, never invoked.
pub fn pair_layouts<I, M, B, T, E>(_: impl FnOnce(I) -> M) -> Result<TaskLayouts, LayoutError>
where
    M: FnOnce(RequestTaskContext) -> B,
    B: Future<Output = Result<T, FrameworkRequestFailure<E>>>,
{
    Ok(TaskLayouts {
        pair_box: output(|(make, context): (M, RequestTaskContext)| {
            super::pair_body(make, context)
        }),
        inner_box: Layout::new::<B>(),
        // F is Sized in the actual wrapper; every field is independent of F.
        future: Layout::new::<
            RequestTaskFuture<std::future::Pending<Result<T, FrameworkRequestFailure<E>>>>,
        >(),
        outcome: Layout::new::<RequestTaskOutcome<T, E>>(),
        join_result: Layout::new::<Result<RequestTaskOutcome<T, E>, tokio::task::JoinError>>(),
        legacy_state: shared(Layout::new::<Mutex<State>>())?,
        root_state: shared(Layout::new::<Mutex<RootState>>())?,
        root_outcome: Layout::new::<RootOutcome<T>>(),
        root_join_result: Layout::new::<Result<RootOutcome<T>, tokio::task::JoinError>>(),
    })
}

/// Pinned dependency ABI, audited by the R0 compiler-type entry. Unsupported
/// cfg/target is an input error, never a guessed zero-size runtime header.
#[derive(Debug, Clone, Copy)]
pub struct TaskBackendLayouts {
    pub header: Layout,
    pub scheduler: Layout,
    pub task_id: Layout,
    pub trailer: Layout,
    pub cell_alignment: usize,
    pub joinset_entry: Layout,
    pub joinset_shared: Layout,
}

/// Computes repr(C) Stage { Running(F), Finished(Result<R,JoinError>), Consumed }
/// and repr(C, align(...)) Cell. `result` is a measured Rust Result layout,
/// not max(sizeof(R),sizeof(JoinError)) with its discriminant forgotten.
pub fn task_cell(
    backend: TaskBackendLayouts,
    future: Layout,
    result: Layout,
) -> Result<Layout, LayoutError> {
    let union = Layout::from_size_align(
        future.size().max(result.size()),
        future.align().max(result.align()),
    )?
    .pad_to_align();
    let stage = Layout::new::<u32>().extend(union)?.0.pad_to_align();
    let core = backend
        .scheduler
        .extend(backend.task_id)?
        .0
        .extend(stage)?
        .0
        .pad_to_align();
    Ok(backend
        .header
        .extend(core)?
        .0
        .extend(backend.trailer)?
        .0
        .align_to(backend.cell_alignment)?
        .pad_to_align())
}

/// Backing table only; the map header is embedded in the process future.
/// Pinned std/hashbrown SSE2 Group width=16. Growth reserves old+new until
/// successful migration; a failed reserve retains the old table unchanged.
pub fn ticket_table(entry: Layout, entries: usize) -> Result<(Layout, usize), LayoutError> {
    if entries == 0 {
        return Ok((Layout::from_size_align(0, 16)?, 0));
    }
    let buckets = if entries < 4 {
        4
    } else if entries < 8 {
        8
    } else {
        entries
            .checked_mul(8)
            .and_then(|n| n.checked_add(6))
            .map(|n| n / 7)
            .and_then(usize::checked_next_power_of_two)
            .ok_or_else(layout_error)?
    };
    let bytes = entry
        .pad_to_align()
        .size()
        .checked_mul(buckets)
        .ok_or_else(layout_error)?;
    let controls = buckets.checked_add(16).ok_or_else(layout_error)?;
    let data = Layout::from_size_align(bytes, entry.align().max(16))?;
    let result = data.extend(Layout::from_size_align(controls, 1)?)?.0;
    Ok((result, buckets))
}

fn layout_error() -> LayoutError {
    Layout::from_size_align(usize::MAX, 2).unwrap_err()
}

/// Checked, allocation-free cost arithmetic. Domain already accounts its own
/// allocations: multiplying counts never authorizes a reservation or creates data.
pub fn bytes(layout: Layout, count: usize) -> Result<usize, LayoutError> {
    layout.size().checked_mul(count).ok_or_else(layout_error)
}
pub fn sum(parts: &[usize]) -> Result<usize, LayoutError> {
    parts.iter().try_fold(0usize, |total, part| {
        total.checked_add(*part).ok_or_else(layout_error)
    })
}

/// Uses the same owned_body as production, not an independently sized async model.
pub fn owned_layouts<O, T, E, I, M>(_: impl FnOnce(I) -> M) -> Result<TaskLayouts, LayoutError>
where
    O: Send + 'static,
    T: 'static,
    E: 'static,
    M: for<'a> FnOnce(&'a mut O, RequestTaskContext) -> RequestTaskBorrowedFuture<'a, T, E>
        + Send
        + 'static,
{
    pair_layouts(|(owner, make): (Arc<tokio::sync::Mutex<O>>, M)| {
        move |context| super::owned_body(owner, make, context)
    })
}