moonwheelkit

    A deterministic hierarchical timing wheel and virtual-time scheduler for MoonBit.

    scheduler
    timing-wheel
    virtual-time
    timer
    deterministic
    Download zip
    Version
    0.2.2
    License
    Apache-2.0
    Last updated
    3 months ago
    Downloads
    49

    No dependencies

    #MoonWheelKit

    MoonWheelKit is a backend-neutral hierarchical timing wheel and deterministic virtual-time scheduler for MoonBit.

    #Install

    moon add brother-666/moonwheelkit

    Import the package as import "brother-666/moonwheelkit" @wheel.

    The core library does not read the system clock. Applications explicitly advance virtual time, which makes timer-heavy services, simulations and tests repeatable across native, JavaScript, WebAssembly and Wasm-GC backends.

    For recovery storms, advance_late_budgeted(target, max_fires) bounds total emissions across all due timers. Remaining due work stays pending and can be drained by calling the method again at the same target tick.

    ///|
    test "schedule with deterministic virtual time" {
    let wheel = @moonwheelkit.Wheel::new()
    ignore(wheel.schedule(@moonwheelkit.TimerSpec::once(3, "expire")))
    match wheel.advance_to(3) {
    @moonwheelkit.AdvanceResult::Advanced(report) => {
    assert_eq(report.fired.length(), 1)
    assert_eq(report.fired[0].payload, "expire")
    }
    @moonwheelkit.AdvanceResult::Rejected(_) =>
    fail("forward advance should succeed")
    }
    }

    match wheel.advance_late_budgeted(1000, 256) {
    @moonwheelkit.DrainResult::Drained(report) => {
    println(report.to_json())
    if report.exhausted {
    // Continue draining at tick 1000 in a later event-loop turn.
    }
    }
    @moonwheelkit.DrainResult::Rejected(_) => ()
    }

    See README.md for scheduling semantics, architecture, ecosystem comparison and reproducible verification commands.

    AdvanceReport

    pub(all) struct AdvanceReport {
    from_tick : Int
    to_tick : Int
    scanned_ticks : Int
    cascades : Int
    fired : Array[FiredTask]
    } derive(Eq,
    Debug
    )

    AdvanceReport::to_json

    fn AdvanceReport::to_json(self : AdvanceReport) -> String

    AdvanceResult

    pub(all) enum AdvanceResult {
    Advanced(AdvanceReport)
    Rejected(ScheduleError)
    } derive(Eq,
    Debug
    )

    BucketEntry

    type BucketEntry

    DrainReport

    pub(all) struct DrainReport {
    from_tick : Int
    to_tick : Int
    budget : Int
    fired : Array[FiredTask]
    deferred_due : Int
    exhausted : Bool
    } derive(Eq,
    Debug
    )

    DrainReport::to_json

    fn DrainReport::to_json(self : DrainReport) -> String

    DrainResult

    pub(all) enum DrainResult {
    Drained(DrainReport)
    Rejected(ScheduleError)
    } derive(Eq,
    Debug
    )

    FiredTask

    pub(all) struct FiredTask {
    timer_id : Int
    scheduled_at : Int
    observed_at : Int
    lateness : Int
    occurrence : Int
    payload : String
    } derive(Eq,
    Debug
    )

    FiredTask::to_json

    fn FiredTask::to_json(self : FiredTask) -> String

    InternalTimer

    type InternalTimer

    MaintenanceReport

    pub(all) struct MaintenanceReport {
    removed_terminal_timers : Int
    removed_stale_entries : Int
    retained_pending_timers : Int
    } derive(Eq,
    Debug
    )

    RepeatMode

    pub(all) enum RepeatMode {
    Once
    FixedDelay
    FixedRate
    } derive(Eq,
    Debug
    )

    ScheduleError

    pub(all) enum ScheduleError {
    InvalidDelay
    InvalidPeriod
    InvalidConfiguration
    InvalidBudget
    UnknownTimer
    TimeWentBackwards
    SnapshotMismatch
    } derive(Eq,
    Debug
    )

    TimerInfo

    pub(all) struct TimerInfo {
    id : Int
    deadline : Int
    period : Int
    repeat : RepeatMode
    payload : String
    state : TimerState
    sequence : Int
    generation : Int
    occurrence : Int
    } derive(Eq,
    Debug
    )

    TimerInfo::to_json

    fn TimerInfo::to_json(self : TimerInfo) -> String

    TimerResult

    pub(all) enum TimerResult {
    Accepted(Int)
    Rejected(ScheduleError)
    } derive(Eq,
    Debug
    )

    TimerSnapshot

    pub(all) struct TimerSnapshot {
    now : Int
    next_id : Int
    next_sequence : Int
    fired_total : Int
    scheduled_total : Int
    cascades_total : Int
    config : WheelConfig
    timers : Array[TimerInfo]
    } derive(Eq,
    Debug
    )

    TimerSnapshot::to_json

    fn TimerSnapshot::to_json(self : TimerSnapshot) -> String

    TimerSpec

    pub(all) struct TimerSpec {
    delay : Int
    period : Int
    repeat : RepeatMode
    payload : String
    } derive(Eq,
    Debug
    )

    TimerSpec::fixed_delay

    fn TimerSpec::fixed_delay(delay : Int, period : Int, payload : String) -> TimerSpec

    TimerSpec::fixed_rate

    fn TimerSpec::fixed_rate(delay : Int, period : Int, payload : String) -> TimerSpec

    TimerSpec::once

    fn TimerSpec::once(delay : Int, payload : String) -> TimerSpec

    TimerState

    pub(all) enum TimerState {
    Pending
    Cancelled
    Fired
    } derive(Eq,
    Debug
    )

    ValidationIssue

    pub(all) struct ValidationIssue {
    code : String
    timer_id : Int
    message : String
    } derive(Eq,
    Debug
    )

    Wheel

    pub struct Wheel {
    config : WheelConfig
    current_tick : Int
    next_id : Int
    next_sequence : Int
    timers : Map[Int, InternalTimer]
    buckets : Array[Array[BucketEntry]]
    fired_total : Int
    scheduled_total : Int
    cascades_total : Int
    }

    Wheel::advance_by

    fn Wheel::advance_by(self : Wheel, delta : Int) -> AdvanceResult

    Wheel::advance_late_budgeted

    fn Wheel::advance_late_budgeted(self : Wheel, target : Int, max_fires : Int) -> DrainResult

    Jumps virtual time while bounding the total number of emitted tasks.

    Due timers are selected globally by (deadline, timer_id). When the budget is exhausted, remaining due timers stay pending and can be drained by calling this method again with the same target.

    Wheel::advance_late_to

    fn Wheel::advance_late_to(self : Wheel, target : Int) -> AdvanceResult

    Wheel::advance_to

    fn Wheel::advance_to(self : Wheel, target : Int) -> AdvanceResult

    Wheel::cancel

    fn Wheel::cancel(self : Wheel, id : Int) -> Bool

    Wheel::compact

    fn Wheel::compact(self : Wheel) -> MaintenanceReport

    Wheel::config

    fn Wheel::config(self : Wheel) -> WheelConfig

    Wheel::get

    fn Wheel::get(self : Wheel, id : Int) -> TimerInfo?

    Wheel::new

    fn Wheel::new(config? : WheelConfig) -> Wheel

    Wheel::now

    fn Wheel::now(self : Wheel) -> Int

    Wheel::pending

    fn Wheel::pending(self : Wheel) -> Int

    Wheel::reschedule

    fn Wheel::reschedule(self : Wheel, id : Int, delay : Int) -> TimerResult

    Wheel::restore

    fn Wheel::restore(snapshot : TimerSnapshot) -> Wheel?

    Wheel::schedule

    fn Wheel::schedule(self : Wheel, spec : TimerSpec) -> TimerResult

    Wheel::snapshot

    fn Wheel::snapshot(self : Wheel) -> TimerSnapshot

    Wheel::stats

    fn Wheel::stats(self : Wheel) -> WheelStats

    Wheel::timers

    fn Wheel::timers(self : Wheel) -> Array[TimerInfo]

    Wheel::validate

    fn Wheel::validate(self : Wheel) -> Array[ValidationIssue]

    WheelConfig

    pub(all) struct WheelConfig {
    tick_ms : Int
    slots_per_level : Int
    levels : Int
    max_catch_up : Int
    } derive(Eq,
    Debug
    )

    WheelConfig::is_valid

    fn WheelConfig::is_valid(self : WheelConfig) -> Bool

    WheelConfig::standard

    fn WheelConfig::standard() -> WheelConfig

    WheelStats

    pub(all) struct WheelStats {
    now : Int
    pending : Int
    cancelled : Int
    fired_total : Int
    scheduled_total : Int
    cascades_total : Int
    levels : Int
    slots_per_level : Int
    } derive(Eq,
    Debug
    )

    WheelStats::to_json

    fn WheelStats::to_json(self : WheelStats) -> String