dscale 0.5.3

A fast & deterministic simulation framework for benchmarking and testing distributed systems
Documentation
use std::cmp::Reverse;

use crate::{
    Jiffies, Pid, ProcessHandle,
    events::{Event, PidEvent, TimedEvent},
    global::local_access::{self, setup_local_access},
    process_handle::ProcessHandlePtr,
    random::Seed,
    runners::{RunStatus, SimulationRunner, common::RunnerCore},
};

pub(crate) struct SeqRunner {
    core: RunnerCore,
    procs: Vec<ProcessHandlePtr>,
}

impl SeqRunner {
    pub(crate) fn new(core: RunnerCore, procs: Vec<ProcessHandlePtr>, seed: Seed) -> Self {
        setup_local_access(seed, None);
        Self { core, procs }
    }

    pub(crate) fn seed_events(&mut self, events: Vec<Reverse<TimedEvent>>) {
        self.core.seed_events(events);
    }

    fn ensure_started(&mut self) {
        if self.core.mark_started() {
            for pid in 0..self.procs.len() {
                self.core.event_queue.push(Reverse(TimedEvent {
                    invocation_time: Jiffies(0),
                    event: Event::Pid {
                        pid,
                        event: PidEvent::Start {
                            base_seed: self.core.seed,
                        },
                    },
                }));
            }
        }
    }

    fn run_loop(&mut self, deadline: Jiffies, max_steps: Option<usize>) -> RunStatus {
        self.ensure_started();
        let mut steps = 0;
        loop {
            if let Some(k) = max_steps {
                if steps >= k {
                    return RunStatus::Completed { steps };
                }
            }
            if self.core.clock.now() >= self.core.time_budget {
                return RunStatus::BudgetExhausted { steps };
            }
            if self.core.clock.now() >= deadline {
                return RunStatus::Completed { steps };
            }
            match self.core.event_queue.pop() {
                None => return RunStatus::NoMoreEvents { steps },
                Some(event) => {
                    steps += self.step(event.0);
                }
            }
        }
    }

    fn step(&mut self, timed_event: TimedEvent) -> usize {
        self.core.advance_time(timed_event.invocation_time);
        match timed_event.event {
            Event::Fault(event) => {
                self.core.handle_fault_event(event);
                0 // Faults does not count in steps
            }
            Event::Pid { pid, event } => {
                if let Some(ready) =
                    self.core
                        .handle_pid_event(timed_event.invocation_time, pid, event)
                {
                    self.dispatch(pid, ready);
                    1
                } else {
                    0
                }
            }
        }
    }

    fn dispatch(&mut self, pid: Pid, event: PidEvent) {
        local_access::prepare(pid, self.core.clock.now());
        match event {
            PidEvent::Start { base_seed } => {
                self.procs[pid].on_start(base_seed + pid as u64); // Change seed a little bit and prevent resonance
            }
            PidEvent::Network {
                source, message, ..
            } => {
                self.procs[pid].on_message(source, message);
            }
            PidEvent::Timer { id } => {
                self.procs[pid].on_timer(id);
            }
        }
        self.core.resolve_events(local_access::take_events());
    }
}

impl SimulationRunner for SeqRunner {
    fn run_full_budget(&mut self) -> RunStatus {
        self.run_loop(self.core.time_budget, None)
    }

    fn run_steps(&mut self, k: usize) -> RunStatus {
        self.run_loop(self.core.time_budget, Some(k))
    }

    fn run_sub_budget(&mut self, sub_budget: Jiffies) -> RunStatus {
        let deadline = self.core.clock.now() + sub_budget;
        self.run_loop(deadline, None)
    }
}