Usage Examples
Relevant source files
This document provides practical examples demonstrating how to use the HandlerTable API in real-world scenarios. It covers common usage patterns, integration strategies, and best practices for implementing lock-free event handling in no_std environments.
For detailed API documentation, see API Reference. For implementation details about the underlying atomic operations, see Implementation Details.
Basic Event Handler Registration
The fundamental usage pattern involves creating a static HandlerTable, registering event handlers, and dispatching events as they occur.
Static Table Creation and Handler Registration
The basic pattern demonstrated in README.md(L11 - L31) shows:
| Operation | Method | Purpose |
|---|---|---|
| Creation | HandlerTable::new() | Initialize empty handler table |
| Registration | register_handler(idx, fn) | Associate handler function with event ID |
| Dispatch | handle(idx) | Execute registered handler for event |
| Cleanup | unregister_handler(idx) | Remove and retrieve handler function |
Sources: README.md(L11 - L31)
Event Registration Patterns
Single Event Handler Registration
The most common pattern involves registering individual handlers for specific event types. Each handler is a closure or function pointer that gets executed when the corresponding event occurs.
sequenceDiagram
participant EventSource as "Event Source"
participant HandlerTableN as "HandlerTable<N>"
participant AtomicUsizeidx as "AtomicUsize[idx]"
participant EventHandlerFn as "Event Handler Fn"
EventSource ->> HandlerTableN: "register_handler(0, closure)"
HandlerTableN ->> AtomicUsizeidx: "compare_exchange(0, fn_ptr)"
alt "Registration Success"
AtomicUsizeidx -->> HandlerTableN: "Ok(0)"
HandlerTableN -->> EventSource: "true"
else "Slot Occupied"
AtomicUsizeidx -->> HandlerTableN: "Err(existing_ptr)"
HandlerTableN -->> EventSource: "false"
end
Note over EventSource,EventHandlerFn: "Later: Event Dispatch"
EventSource ->> HandlerTableN: "handle(0)"
HandlerTableN ->> AtomicUsizeidx: "load(Ordering::Acquire)"
AtomicUsizeidx -->> HandlerTableN: "fn_ptr"
HandlerTableN ->> EventHandlerFn: "transmute + call()"
EventHandlerFn -->> HandlerTableN: "execution complete"
HandlerTableN -->> EventSource: "true"
Sources: README.md(L16 - L21)
Multiple Handler Registration
For systems handling multiple event types, handlers are typically registered during initialization:
flowchart TD
subgraph subGraph2["Handler Functions"]
TimerFn["timer_interrupt_handler"]
IOFn["io_completion_handler"]
UserFn["user_signal_handler"]
end
subgraph subGraph1["HandlerTable Operations"]
Reg0["register_handler(0, timer_fn)"]
Reg1["register_handler(1, io_fn)"]
Reg2["register_handler(2, user_fn)"]
end
subgraph subGraph0["System Initialization Phase"]
Init["System Init"]
Timer["Timer Handler Setup"]
IO["I/O Handler Setup"]
User["User Event Setup"]
end
IO --> Reg1
Init --> IO
Init --> Timer
Init --> User
Reg0 --> TimerFn
Reg1 --> IOFn
Reg2 --> UserFn
Timer --> Reg0
User --> Reg2
Sources: README.md(L16 - L21)
Event Handling Scenarios
Successful Event Dispatch
When an event occurs and a handler is registered, the handle method executes the associated function and returns true:
flowchart TD
subgraph subGraph0["Example from README"]
Ex1["handle(0) -> true"]
Ex2["handle(2) -> false"]
end
Event["Event Occurs"]
Handle["handle(event_id)"]
Check["Check AtomicUsize[event_id]"]
Found["Handler Found?"]
Execute["Execute Handler Function"]
ReturnFalse["Return false"]
ReturnTrue["Return true"]
Check --> Found
Event --> Handle
Execute --> ReturnTrue
Found --> Execute
Found --> ReturnFalse
Handle --> Check
The example in README.md(L23 - L24) demonstrates both successful dispatch (handle(0) returns true) and missing handler scenarios (handle(2) returns false).
Sources: README.md(L23 - L24)
Handler Retrieval and Cleanup
The unregister_handler method provides atomic removal and retrieval of handler functions:
flowchart TD
subgraph subGraph2["Example Calls"]
Unreg1["unregister_handler(2) -> None"]
Unreg2["unregister_handler(1) -> Some(fn)"]
end
subgraph subGraph1["Return Values"]
Some["Some(handler_fn)"]
None["None"]
end
subgraph subGraph0["Unregistration Process"]
Call["unregister_handler(idx)"]
Swap["atomic_swap(0)"]
Check["Check Previous Value"]
Result["Option"]
end
Call --> Swap
Check --> Result
None --> Unreg1
Result --> None
Result --> Some
Some --> Unreg2
Swap --> Check
The pattern shown in README.md(L26 - L28) demonstrates retrieving a previously registered handler function, which can then be called directly.
Sources: README.md(L26 - L30)
Advanced Usage Patterns
Concurrent Handler Management
In multi-threaded environments, multiple threads can safely register, handle, and unregister events without synchronization primitives:
flowchart TD
subgraph subGraph2["Memory Ordering"]
Acquire["Acquire Ordering"]
Release["Release Ordering"]
Consistency["Memory Consistency"]
end
subgraph subGraph1["Atomic Operations Layer"]
CAS["compare_exchange"]
Load["load(Acquire)"]
Swap["swap(Acquire)"]
end
subgraph subGraph0["Thread Safety Guarantees"]
T1["Thread 1: register_handler"]
T2["Thread 2: handle"]
T3["Thread 3: unregister_handler"]
end
Acquire --> Consistency
CAS --> Release
Load --> Acquire
Release --> Consistency
Swap --> Acquire
T1 --> CAS
T2 --> Load
T3 --> Swap
Sources: README.md(L14)
Static vs Dynamic Handler Tables
The compile-time sized HandlerTable<N> enables static allocation suitable for no_std environments:
| Approach | Declaration | Use Case |
|---|---|---|
| Static Global | static TABLE: HandlerTable<8> | System-wide event handling |
| Local Instance | let table = HandlerTable::<16>::new() | Component-specific events |
| Const Generic | HandlerTable<MAX_EVENTS> | Configurable event capacity |
The static approach shown in README.md(L14) is preferred for kernel-level integration where heap allocation is unavailable.
Sources: README.md(L14)
Integration Examples
ArceOS Kernel Integration
In the ArceOS operating system context, HandlerTable serves as the central event dispatch mechanism:
flowchart TD
subgraph subGraph2["Event Dispatch Flow"]
Dispatch["event_dispatch(id)"]
Handle["TABLE.handle(id)"]
Execute["handler_function()"]
end
subgraph subGraph1["HandlerTable Integration"]
TABLE["static KERNEL_EVENTS: HandlerTable<32>"]
IRQ["IRQ_HANDLERS[0..15]"]
TIMER["TIMER_EVENTS[16..23]"]
SCHED["SCHED_EVENTS[24..31]"]
end
subgraph subGraph0["ArceOS Kernel Components"]
Interrupt["Interrupt Controller"]
Timer["Timer Subsystem"]
Scheduler["Task Scheduler"]
Syscall["System Call Handler"]
end
Dispatch --> Handle
Handle --> Execute
IRQ --> TABLE
Interrupt --> IRQ
SCHED --> TABLE
Scheduler --> SCHED
Syscall --> TABLE
TABLE --> Dispatch
TIMER --> TABLE
Timer --> TIMER
Sources: README.md(L7)
Device Driver Event Handling
Device drivers can use dedicated handler tables for managing I/O completion events:
flowchart TD
subgraph subGraph2["Event Types"]
Complete["DMA_COMPLETE"]
Error["DMA_ERROR"]
Timeout["DMA_TIMEOUT"]
end
subgraph subGraph1["Event Management"]
DriverTable["HandlerTable"]
CompletionFn["io_completion_handler"]
ErrorFn["io_error_handler"]
end
subgraph subGraph0["Driver Architecture"]
Driver["Device Driver"]
DMA["DMA Controller"]
Buffer["I/O Buffers"]
end
Complete --> CompletionFn
CompletionFn --> Buffer
DMA --> Complete
DMA --> Error
DMA --> Timeout
Driver --> DriverTable
Error --> ErrorFn
ErrorFn --> Buffer
Timeout --> ErrorFn
This pattern allows drivers to maintain separate event handling contexts while leveraging the same lock-free infrastructure.
Sources: README.md(L7)