dope-runtime 0.25.0

Thread-per-core execution and process lifecycle for dope
Documentation
use std::marker;

use dope_core::{
    platform::{affinity, affinity::Binding, channel, thread},
    random,
};

use crate::process::{
    self,
    failure::bus::{Event, Fault, Read, Report},
    layout,
    runtime::{bus, worker, worker::Entry},
};

pub(super) enum Outcome<X> {
    Complete,
    Affinity {
        source: affinity::Error,
        bus: Report,
    },
    Worker {
        source: X,
        bus: Report,
    },
    Bus(Fault),
}

fn execute<I, E, X>(
    job: layout::Assignment<I>,
    tx: bus::Tx<'_>,
    seed: random::Seed,
    entry: E,
) -> (Outcome<X>, Event)
where
    E: Entry<I, X>,
{
    let layout::Assignment { cpu, input } = job;
    let affinity = match Binding::bind(cpu) {
        Ok(affinity) => affinity,
        Err(error) => {
            return (
                Outcome::Affinity {
                    source: error,
                    bus: tx.report(),
                },
                Event::Failed,
            );
        }
    };
    let context = process::Context::new(affinity, seed, tx);
    match entry.run(input, context) {
        Ok(proof) => match proof.terminal(tx.report()) {
            Err(failure) => (Outcome::Bus(failure), Event::Failed),
            Ok(event) => (Outcome::Complete, event),
        },
        Err(error) => (
            Outcome::Worker {
                source: error,
                bus: tx.report(),
            },
            Event::Failed,
        ),
    }
}

pub(in crate::process::runtime) struct Task<I, E, X> {
    assignment: layout::Assignment<I>,
    peer: bus::Bus,
    seed: random::Seed,
    entry: E,
    output: marker::PhantomData<fn() -> X>,
}

pub(in crate::process::runtime) struct Parent {
    pub(in crate::process::runtime) main: Option<bus::Main>,
    pub(in crate::process::runtime) cpu: affinity::Cpu,
    pub(super) handoff: Option<Read>,
}

pub(in crate::process::runtime) struct Output<X> {
    pub(super) outcome: Outcome<X>,
    pub(super) terminal: Option<Fault>,
}

impl<I, E, X> Task<I, E, X> {
    pub(super) fn try_new(
        job: layout::Assignment<I>,
        seed: random::Seed,
        entry: E,
    ) -> Result<(Self, Parent), channel::Create> {
        let cpu = job.cpu;
        let (main, peer) = bus::Main::try_pair()?;
        Ok((
            Self {
                assignment: job,
                peer,
                seed,
                entry,
                output: marker::PhantomData,
            },
            Parent {
                main: Some(main),
                cpu,
                handoff: None,
            },
        ))
    }
}

impl<I: Send, E: worker::Entry<I, X>, X: Send> thread::Task for Task<I, E, X> {
    type Output = Output<X>;
    fn run(self) -> Self::Output {
        let Self {
            assignment: job,
            peer,
            seed,
            entry,
            output: _,
        } = self;
        let (outcome, event) = execute::<I, E, X>(job, peer.tx(), seed, entry);
        let terminal = peer.tx().send(event).err();
        Output { outcome, terminal }
    }
}

impl Parent {
    pub(super) fn stop(&mut self) -> Option<dope_core::Errno> {
        let source = self.main.as_ref()?.stop()?;
        self.main = None;
        self.handoff = Some(Read::Stopped(source));
        Some(source)
    }

    pub(super) fn handoff<M: bus::Mode>(&mut self, readiness: bus::Readiness) {
        if let Some(main) = self.main.take() {
            self.handoff = main.handoff::<M>(readiness);
        }
    }
}