Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ jobs:
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
rust-toolchain: [nightly-2025-05-20]
targets:
- x86_64-unknown-none
- riscv64gc-unknown-none-elf
- aarch64-unknown-none
- aarch64-unknown-none-softfloat
- loongarch64-unknown-none
- loongarch64-unknown-none-softfloat

steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
[![Docs.rs](https://docs.rs/handler_table/badge.svg)](https://docs.rs/handler_table)
[![CI](https://github.com/arceos-org/handler_table/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/handler_table/actions/workflows/ci.yml)

A lock-free table of event handlers.
A lock-free, `no_std`, fixed-capacity table of event handlers.

## Features

- no_std, no heap allocation
- Lock-free registration, unregistration and invocation
- Generic capacity `N` known at compile time
- Suitable for bare-metal and OS-kernel contexts

## Examples

Expand All @@ -13,12 +20,12 @@ use handler_table::HandlerTable;

static TABLE: HandlerTable<8> = HandlerTable::new();

TABLE.register_handler(0, || {
assert!(TABLE.register_handler(0, || {
println!("Hello, event 0!");
});
TABLE.register_handler(1, || {
}));
assert!(TABLE.register_handler(1, || {
println!("Hello, event 1!");
});
}));

assert!(TABLE.handle(0)); // print "Hello, event 0!"
assert!(!TABLE.handle(2)); // unregistered
Expand Down
12 changes: 12 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[toolchain]
profile = "minimal"
channel = "nightly-2025-05-20"
components = ["rust-src", "llvm-tools", "rustfmt", "clippy"]
targets = [
"x86_64-unknown-none",
"riscv64gc-unknown-none-elf",
"aarch64-unknown-none",
"aarch64-unknown-none-softfloat",
"loongarch64-unknown-none",
"loongarch64-unknown-none-softfloat",
]
48 changes: 37 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,36 @@ use core::sync::atomic::{AtomicUsize, Ordering};

/// The type of an event handler.
///
/// Currently no arguments and return values are supported.
/// Currently, only no arguments and return values are supported.
pub type Handler = fn();

/// A lock-free table of event handlers.
///
/// It internally uses an array of `AtomicUsize` to store the handlers.
/// Internally stores up to `N` function pointers in an array of `AtomicUsize`.
/// All operations are O(1), and are safe for concurrent use in `no_std`.
///
/// # Type Parameters
/// - `N`: Number of handler slots (must be > 0).
pub struct HandlerTable<const N: usize> {
handlers: [AtomicUsize; N],
}

impl<const N: usize> HandlerTable<N> {
/// Creates a new handler table with all entries empty.
/// Creates a new `HandlerTable` with all slots empty.
pub const fn new() -> Self {
Self {
handlers: [const { AtomicUsize::new(0) }; N],
}
}

/// Registers a handler for the given index.
/// Attempts to register `handler` in slot `idx`.
///
/// - `idx`: Slot index (0 ≤ `idx` < `N`).
/// - `handler`: Function pointer to register.
///
/// Returns `true` if the registration succeeds, `false` if the index is out
/// of bounds or the handler is already registered.
/// # Returns
/// - `true` if the slot was empty and registration succeeded.
/// - `false` if `idx` is out of range or the slot was already occupied.
pub fn register_handler(&self, idx: usize, handler: Handler) -> bool {
if idx >= N {
return false;
Expand All @@ -36,9 +44,17 @@ impl<const N: usize> HandlerTable<N> {
.is_ok()
}

/// Unregisters the handler for the given index.
/// Unregisters and returns the handler in slot `idx`.
///
/// # Parameters
/// - `idx`: Slot index (0 ≤ `idx` < `N`).
///
/// # Returns
/// - `Some(handler)` if a handler was registered.
/// - `None` if `idx` is out of range or the slot was empty.
///
/// Returns the existing handler if it is registered, `None` otherwise.
/// # Concurrency
/// Lock-free and thread-safe: uses atomic swap.
pub fn unregister_handler(&self, idx: usize) -> Option<Handler> {
if idx >= N {
return None;
Expand All @@ -51,10 +67,20 @@ impl<const N: usize> HandlerTable<N> {
}
}

/// Handles the event with the given index.
/// Invokes the handler in slot `idx`.
///
/// # Parameters
/// - `idx`: Slot index (0 ≤ `idx` < `N`).
///
/// # Returns
/// - `true` if a handler was found and called.
/// - `false` if `idx` is out of range or the slot was empty.
///
/// # Concurrency
/// Lock-free and thread-safe: uses atomic load.
///
/// Returns `true` if the event is handled, `false` if no handler is
/// registered for the given index.
/// # Panics
/// Panics if the handler itself panics.
pub fn handle(&self, idx: usize) -> bool {
if idx >= N {
return false;
Expand Down
65 changes: 65 additions & 0 deletions tests/handler_tabler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use core::sync::atomic::{AtomicBool, Ordering};
use handler_table::HandlerTable;

static CALLED: AtomicBool = AtomicBool::new(false);

fn handler() {
CALLED.store(true, Ordering::SeqCst);
}

#[test]
fn test_default() {
let table = HandlerTable::<3>::default();
assert!(!table.handle(0)); // Should be empty
}

#[test]
fn test_register_and_handle() {
CALLED.store(false, Ordering::SeqCst);
let table = HandlerTable::<4>::new();

// Register and call handler
assert!(table.register_handler(1, handler));
assert!(table.handle(1));
assert!(CALLED.load(Ordering::SeqCst));

// Duplicate registration should fail
assert!(!table.register_handler(1, handler));
}

#[test]
fn test_unregister_and_handle() {
CALLED.store(false, Ordering::SeqCst);
let table = HandlerTable::<4>::new();

// Register handler
assert!(table.register_handler(2, handler));

// Unregister and get the original handler, calling it should set CALLED
let h = table.unregister_handler(2).expect("should have handler");
h();
assert!(CALLED.load(Ordering::SeqCst));

// After unregistering, handle should return false
CALLED.store(false, Ordering::SeqCst);
assert!(!table.handle(2));
assert!(!CALLED.load(Ordering::SeqCst));
}

#[test]
fn test_out_of_bounds() {
let table = HandlerTable::<2>::new();

// Out-of-bounds operations should fail or return None
assert!(!table.register_handler(2, handler));
assert!(table.unregister_handler(2).is_none());
assert!(!table.handle(2));
}

#[test]
fn test_unregister_empty_slot() {
let table = HandlerTable::<4>::new();

// Unregistering from empty slot should return None
assert!(table.unregister_handler(1).is_none());
}