moonwheelkit

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

scheduler
timing-wheel
virtual-time
timer
deterministic
moon add brother-666/moonwheelkit@0.2.2
Download zip
Version
0.2.2
License
Apache-2.0
Last updated
28 days ago
Downloads
16
README

#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

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io