Module System

Module traits, registry, define_module! macro, lifecycle management, and ABI.

Documentation

Module Overview

The module system (helix-modules, 9 source files, ~3,800 lines) provides a pluggable, hot-reloadable component framework for the kernel. Modules can implement schedulers, allocators, filesystems, drivers, and more — all loadable and replaceable at runtime.

Key Features

  • Two API versions — v1 (trait-based) and v2 (const metadata + event-driven)
  • Hot-reload — replace module binaries without downtime, preserving state
  • ABI versioning — compatibility checking prevents loading incompatible modules
  • Dependency resolution — automatic topological sort with cycle detection
  • Module registry — lookup by ID, name, or capability
  • Interface system — type-erased message passing between modules
  • Three binary formats — ELF, HelixNative, WebAssembly (future)

Source Layout

Module System — Source Tree10N · 9E
modules/src/Module system core9lib.rsModule trait v1, Mod…1abi.rsAbiVersion, AbiCheck…1dependencies.rsDependencyGraph, cyc…1hot_reload.rsHotReloadEngine, Rel…1interface.rsModuleMessage, Inter…1loader.rsModuleFormat, ElfLoa…1registry.rsModuleRegistry — nam…1v2.rsModuleTrait v2, Cont…1v2_tests.rsLifecycle tests for …1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Module Trait (v1)

The original module API used by most existing modules:

modules/src/module.rs
rust
1
pub trait Module: Send + Sync {
3
/// Static metadata about this module.
2 refs
fn metadata(&self) -> &ModuleMetadata;
5
6
/// Initialize the module. Called once after loading.
fn init(&mut self, ctx: &ModuleContext) -> Result<(), ModuleError>;
8
9
/// Start the module. Called after all dependencies are initialized.
fn start(&mut self) -> Result<(), ModuleError>;
11
12
/// Stop the module. Called before unloading.
fn stop(&mut self) -> Result<(), ModuleError>;
14
15
/// Clean up all resources.
fn cleanup(&mut self) -> Result<(), ModuleError>;
17
18
/// Return true if the module is functioning correctly.
fn is_healthy(&self) -> bool;
20
21
/// Serialize current state for hot-reload.
fn get_state(&self) -> Option<ModuleState> { None }
23
24
/// Restore state from a previous instance.
fn restore_state(&mut self, _state: ModuleState) -> Result<(), ModuleError> {
26
Err(ModuleError::NotSupported)
27
}
28
29
/// Handle an incoming message from another module.
fn handle_message(&mut self, _msg: ModuleMessage) -> Result<ModuleMessage, ModuleError> {
31
Err(ModuleError::NotSupported)
32
}
33
}
34
Index

ModuleMetadata

modules/src/module.rs
rust
1
pub struct ModuleMetadata {
3
pub name: &'static str,
4
pub version: &'static str,
5
pub author: &'static str,
6
pub description: &'static str,
7
pub flags: ModuleFlags,
8
pub dependencies: Vec<ModuleDependency>,
9
pub abi_version: AbiVersion,
10
}
11
Index

ModuleFlags

modules/src/module.rs
rust
1
2
bitflags! {
pub struct ModuleFlags: u32 {
4
const ESSENTIAL = 1 << 0; // Cannot be unloaded
5
const HOT_RELOADABLE = 1 << 1; // Supports live replacement
6
const USERSPACE = 1 << 2; // Runs in user mode
7
const DRIVER = 1 << 3; // Hardware driver
8
const FILESYSTEM = 1 << 4; // Filesystem implementation
9
const SCHEDULER = 1 << 5; // Scheduling algorithm
10
const ALLOCATOR = 1 << 6; // Memory allocator
11
const SECURITY = 1 << 7; // Security module
12
}
13
}
14
Index

define_module! Macro

modules/src/macros.rs
rust
1
2
define_module! {
3
name: "my_module",
4
version: "1.0.0",
5
author: "Helix Team",
6
description: "Example module",
7
flags: ModuleFlags::HOT_RELOADABLE,
8
dependencies: [("helix-hal", ">=0.4.0")],
9
init: my_init_fn,
10
}
11

The macro generates the Module implementation boilerplate and exports the module entry point symbol.


Module Trait (v2)

The v2 API uses const metadata (computed at compile time) and an event-driven model:

modules/src/v2/trait.rs
rust
1
pub trait ModuleTrait: Send + Sync {
3
/// Const metadata — no runtime allocation.
fn info(&self) -> ModuleInfo;
5
fn init(&mut self, ctx: &Context) -> Result<(), ModuleError>;
fn start(&mut self) -> Result<(), ModuleError>;
fn stop(&mut self) -> Result<(), ModuleError>;
9
10
/// Handle kernel events (tick, shutdown, memory pressure, etc.)
fn handle_event(&mut self, _event: &Event) -> EventResponse {
12
EventResponse::Ignored
13
}
14
15
/// Handle a typed request from another module.
fn handle_request(&mut self, _request: &Request) -> Result<Response, ModuleError> {
17
Ok(Response::err("Not implemented"))
18
}
19
fn is_healthy(&self) -> bool { true }
fn save_state(&self) -> Option<Vec<u8>> { None }
fn restore_state(&mut self, _state: &[u8]) -> Result<(), ModuleError> { Ok(()) }
23
}
24
Index

ModuleInfo (Const)

modules/src/v2/info.rs
rust
1
2 refs
pub struct ModuleInfo {
3
pub name: &'static str,
4
pub version: ModuleVersion,
5
pub description: &'static str,
6
pub author: &'static str,
7
pub license: &'static str,
8
pub flags: ModuleFlags,
9
pub dependencies: &'static [&'static str],
10
pub provides: &'static [&'static str],
11
}
12
13
// Const builder — metadata computed at compile time
const INFO: ModuleInfo = ModuleInfo::new("round_robin_scheduler")
15
.version(1, 0, 0)
16
.author("Helix Team")
17
.description("Round-robin scheduling module")
18
.flags(ModuleFlags::SCHEDULER.union(ModuleFlags::HOT_RELOADABLE));
19
Index

Events

modules/src/v2/event.rs
rust
1
pub enum Event {
3
Tick { timestamp_ns: u64 },
4
Shutdown,
5
MemoryPressure { level: MemoryPressureLevel },
6
CpuHotplug { cpu_id: u32, online: bool },
7
Custom { name: String, data: Vec<u8> },
8
}
9
pub enum EventResponse {
11
Handled,
12
Ignored,
13
Error(String),
14
}
15
Index

v1 Compatibility

The ModuleAdapter wraps a v2 module to present a v1 Module interface, enabling gradual migration:

modules/src/v2/adapter.rs
rust
1
2 refs
pub struct ModuleAdapter<T: ModuleTrait> {
3
inner: T,
4
context: Context,
5
}
6
2 refs
impl<T: ModuleTrait> Module for ModuleAdapter<T> {
8
// Delegates to T's ModuleTrait methods
9
}
10
Index

module_v2! Macro

modules/src/v2/macros.rs
rust
1
2
module_v2! {
3
type: RoundRobinModule,
4
info: INFO,
5
}
6

ABI Management

The ABI system ensures binary compatibility between module versions.

ABI Version

modules/src/abi.rs
rust
1
pub struct AbiVersion {
3
pub major: u16, // Incompatible changes
4
pub minor: u16, // Backward-compatible additions
5
pub patch: u16, // Bug fixes only
6
}
7
Index

Compatibility Rules

Old → NewCompatible?Reason
1.0.0 → 1.0.1YesPatch bump
1.0.0 → 1.1.0YesMinor bump (additive)
1.0.0 → 2.0.0NoMajor bump (breaking)
1.2.0 → 1.1.0NoMinor downgrade

Symbol Table

modules/src/abi.rs
rust
1
pub struct SymbolTable {
3
pub symbols: Vec<SymbolEntry>,
4
}
5
2 refs
pub struct SymbolEntry {
7
pub name: &'static str,
8
pub kind: SymbolKind, // Function, Variable, Type
9
pub address: usize,
10
pub size: usize,
11
pub deprecated: bool,
12
}
13
Index

The AbiChecker compares old and new symbol tables during hot-reload:

  • All non-deprecated symbols from the old version must exist in the new version
  • Function signatures must be compatible (same argument count and types)
  • New symbols may be added (minor version bump)

Dependency Resolution

The dependency system uses a directed acyclic graph (DAG) to manage module load order.

DependencyGraph

modules/src/dependency.rs
rust
1
pub struct DependencyGraph {
pub fn add_module(&mut self, id: ModuleId, deps: &[ModuleDependency]);
pub fn remove_module(&mut self, id: ModuleId);
pub fn topological_sort(&self) -> Result<Vec<ModuleId>, CycleError>;
pub fn has_cycle(&self) -> bool;
pub fn dependents(&self, id: ModuleId) -> Vec<ModuleId>;
pub fn dependencies(&self, id: ModuleId) -> Vec<ModuleId>;
9
}
10
Index

Version Constraints

modules/src/dependency.rs
rust
1
pub struct ModuleDependency {
3
pub name: &'static str,
4
pub version_constraint: &'static str, // ">=0.4.0", "^1.0", "=2.0.0"
5
}
6
Index

The DependencyResolver verifies that all version constraints are satisfiable before loading begins. If not, it reports the conflicting requirements.

Cycle Detection

The graph uses Depth-First Search with three colors (white/gray/black) to detect cycles. If a gray node is encountered during traversal, a cycle exists and the offending path is reported.


Hot-Reload Engine

The hot-reload engine manages the full lifecycle of replacing a running module.

State Machine

Hot-Reload Engine — State Machine8N · 7E
successfailureIdleWaiting for reload t…1PreparingNotify dependents, q…2SavingStateCall get_state() on …2UnloadingRemove old binary fr…2LoadingLoad new binary, ver…2RestoringStateCall restore_state()…3CompletedResume normal operat…1RollbackFailedRecovery failed1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

API

modules/src/hot_reload.rs
rust
1
pub struct HotReloadEngine {
pub fn reload(&mut self, module_id: ModuleId,
4
new_binary: &[u8]) -> Result<ReloadResult>;
pub fn state(&self) -> ReloadState;
pub fn cancel(&mut self) -> Result<()>;
7
}
8
2 refs
pub struct ReloadResult {
10
pub module_id: ModuleId,
11
pub old_version: AbiVersion,
12
pub new_version: AbiVersion,
13
pub state_preserved: bool,
14
pub duration: Duration,
15
}
16
Index

Rollback

If the new module fails to initialize or restore state:

  1. The engine attempts to reload the old binary
  2. Old state is restored from the saved snapshot
  3. If rollback also fails, the module is marked as RollbackFailed and the self-healing system is notified

Module Registry

The ModuleRegistry provides fast lookup by multiple keys:

modules/src/registry.rs
rust
1
2 refs
pub struct ModuleRegistry {
3
// Primary: ModuleId → Module
4
modules: BTreeMap<ModuleId, Box<dyn Module>>,
5
6
// Index: name → ModuleId
7
by_name: BTreeMap<String, ModuleId>,
8
9
// Index: capability → Vec<ModuleId>
10
by_capability: BTreeMap<ModuleFlags, Vec<ModuleId>>,
11
}
12
2 refs
impl ModuleRegistry {
pub fn register(&mut self, module: Box<dyn Module>) -> Result<ModuleId>;
pub fn unregister(&mut self, id: ModuleId) -> Result<Box<dyn Module>>;
pub fn get(&self, id: ModuleId) -> Option<&dyn Module>;
pub fn get_mut(&mut self, id: ModuleId) -> Option<&mut dyn Module>;
pub fn find_by_name(&self, name: &str) -> Option<ModuleId>;
pub fn find_by_capability(&self, flag: ModuleFlags) -> Vec<ModuleId>;
pub fn all(&self) -> Vec<ModuleId>;
pub fn count(&self) -> usize;
22
}
23
Index

Module Loader

The loader system supports multiple binary formats:

Formats

modules/src/loader.rs
rust
1
pub enum ModuleFormat {
3
Elf, // Standard ELF64 shared object
4
HelixNative, // Helix-specific optimized format
5
Wasm, // WebAssembly (future)
6
}
7
Index

Loader Trait

modules/src/loader.rs
rust
1
pub trait ModuleLoader: Send + Sync {
fn format(&self) -> ModuleFormat;
fn can_load(&self, data: &[u8]) -> bool;
fn load(&self, data: &[u8]) -> Result<LoadedModule, LoadError>;
fn unload(&self, module: &LoadedModule) -> Result<()>;
7
}
8
3 refs
pub struct LoadedModule {
10
pub base_address: usize,
11
pub size: usize,
12
pub entry_point: usize,
13
pub symbols: SymbolTable,
14
}
15
Index

LoaderRegistry

modules/src/loader.rs
rust
1
2 refs
pub struct LoaderRegistry {
3
loaders: Vec<Box<dyn ModuleLoader>>,
4
}
5
2 refs
impl LoaderRegistry {
pub fn register(&mut self, loader: Box<dyn ModuleLoader>);
pub fn load(&self, data: &[u8]) -> Result<LoadedModule>; // Auto-detects format
9
}
10
Index

Interface System

Modules communicate through a type-erased message-passing interface.

Messages

modules/src/interface.rs
rust
1
pub struct ModuleMessage {
3
pub msg_type: MessageType,
4
pub payload: MessagePayload,
5
}
6
2 refs
pub enum MessageType {
8
Request,
9
Response,
10
Notification,
11
Error,
12
}
13
2 refs
pub enum MessagePayload {
15
Empty,
16
Text(String),
17
Binary(Vec<u8>),
18
Typed(Box<dyn Any + Send>),
19
}
20
Index

ModuleInterface Trait

modules/src/interface.rs
rust
1
pub trait ModuleInterface: Send + Sync {
fn interface_name(&self) -> &'static str;
fn handle_message(&mut self, msg: ModuleMessage) -> Result<ModuleMessage>;
5
}
6
Index

Standard Interfaces

Interface NamePurposeMethods
schedulerThread schedulingadd_thread, remove, next, yield, tick
allocatorMemory allocationalloc, dealloc, realloc, stats
filesystemFile operationsopen, close, read, write, stat
block_deviceBlock I/Oread_block, write_block, flush
networkNetwork stacksend, recv, bind, listen
securityAccess controlcheck, grant, revoke

InterfaceRegistry

modules/src/interface.rs
rust
1
pub struct InterfaceRegistry {
pub fn register(&mut self, name: &str,
4
interface: Box<dyn ModuleInterface>) -> Result<()>;
pub fn unregister(&mut self, name: &str) -> Result<()>;
pub fn get(&self, name: &str) -> Option<&dyn ModuleInterface>;
pub fn get_mut(&mut self, name: &str) -> Option<&mut dyn ModuleInterface>;
pub fn list(&self) -> Vec<&str>;
9
}
10
Index

Example: Round-Robin Scheduler

The helix-scheduler-round-robin crate (modules_impl/schedulers/round_robin/, 3 files, ~400 lines) is a complete example of a v2 module implementing the Scheduler trait.

Structure

Round-Robin Scheduler — Source Tree4N · 3E
round_robin/Round-Robin schedule…3lib.rsRoundRobinModule — v…1config.rsRoundRobinConfig — t…1scheduler.rsRoundRobinScheduler …1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Configuration

modules_impl/schedulers/round_robin/src/lib.rs
rust
1
pub struct RoundRobinConfig {
3
pub base_time_slice_ms: u64, // Default: 10ms
4
pub priority_scaling: bool, // Scale time slice by priority?
5
pub load_balance_interval: u64, // Ticks between load balancing
6
pub min_time_slice_ms: u64, // Minimum quantum
7
pub max_time_slice_ms: u64, // Maximum quantum
8
}
9
Index

When priority_scaling is enabled, higher-priority threads get longer time slices:

  • Priority 0-30: 0.5x base (5 ms)
  • Priority 31-60: 1.0x base (10 ms)
  • Priority 61-90: 1.5x base (15 ms)
  • Priority 91+: 2.0x base (20 ms)

Scheduling Algorithm

  1. Each CPU has a FIFO queue of ready threads
  2. On each timer tick, decrement the current thread's remaining quantum
  3. When quantum expires, move the current thread to the back of the queue
  4. Pick the next thread from the front of the queue
  5. Periodically, load balance: migrate threads from overloaded CPUs to underloaded ones