dscale 0.7.1

A fast & deterministic simulation framework for benchmarking and testing distributed systems
Documentation
// DScale: deterministic distributed systems simulator
// Copyright (C) 2026  Konstantin Shprenger

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::{cmp::Reverse, hint};

use crate::{
    Jiffies, Pid, Process,
    events::{Event, PidEvent, TimedEvent},
    process::ProcessPtr,
    random::Seed,
    runners::{CompleteStatus, SimulationRunner, core::RunnerCore},
    services::process_access::{self, setup_process_access},
};

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

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

    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>) -> CompleteStatus {
        self.ensure_started();
        self.core.steps_made = 0;
        self.core.max_steps = max_steps;
        self.core.deadline = deadline;
        loop {
            if let Some(complete_status) = self.core.check_deadline() {
                return complete_status;
            }
            if let Some(complete_status) = self.core.check_steps() {
                return complete_status;
            }
            match self.core.event_queue.pop() {
                None => {
                    hint::cold_path();
                    return CompleteStatus::NoMoreEvents {
                        steps: self.core.steps_made,
                    };
                }
                Some(event) => {
                    self.step(event.0);
                }
            }
        }
    }

    fn step(&mut self, timed_event: TimedEvent) {
        self.core.advance_time(timed_event.invocation_time);
        match timed_event.event {
            Event::Fault(event) => {
                hint::cold_path();
                self.core.handle_fault_event(event);
                // 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.execute(pid, ready);
                    self.core.steps_made += 1;
                }
            }
        }
    }

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

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

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

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