| Crates.io | kestrel-timer |
| lib.rs | kestrel-timer |
| version | 0.3.6 |
| created_at | 2025-10-31 16:18:18.02212+00 |
| updated_at | 2025-12-27 09:38:56.876831+00 |
| description | High-performance async timer library based on Hierarchical Timing Wheel algorithm |
| homepage | https://github.com/ShaoG-R/kestrel-timer |
| repository | https://github.com/ShaoG-R/kestrel-timer |
| max_upload_size | |
| id | 1910274 |
| size | 505,465 |
High-performance async timer library based on Hierarchical Timing Wheel algorithm
kestrel-timer is a high-performance async timer library based on the Hierarchical Timing Wheel algorithm, designed for Rust and Tokio runtime. It provides O(1) time complexity for timer operations and easily handles 10,000+ concurrent timers.
Core Advantages:
DeferredMap (generational arena) for task indexing with O(1) operationsparking_lot::Mutex for efficient lockingAdd to your Cargo.toml:
[dependencies]
kestrel-timer = "0.2.0"
tokio = { version = "1.48", features = ["full"] }
use kestrel_timer::{TimerWheel, CallbackWrapper, TimerTask};
use std::time::Duration;
#[tokio::main]
async fn main() {
// Create timer with default config
let timer = TimerWheel::with_defaults();
// Step 1: Allocate handle
let handle = timer.allocate_handle();
let task_id = handle.task_id();
// Step 2: Create task
let callback = Some(CallbackWrapper::new(|| async {
println!("Timer fired!");
}));
let task = TimerTask::new_oneshot(Duration::from_secs(1), callback);
// Step 3: Register task
let timer_handle = timer.register(handle, task).unwrap();
// Wait for completion or cancel
// timer_handle.cancel();
}
use kestrel_timer::{TimerWheel, CallbackWrapper, TimerTask};
use std::time::Duration;
let timer = TimerWheel::with_defaults();
// Step 1: Batch allocate handles
let handles = timer.allocate_handles(100);
let task_ids: Vec<_> = handles.iter().map(|h| h.task_id()).collect();
// Step 2: Create tasks
let tasks: Vec<_> = (0..100)
.map(|i| {
let delay = Duration::from_millis(100 + i * 10);
let callback = Some(CallbackWrapper::new(move || async move {
println!("Timer {} fired", i);
}));
TimerTask::new_oneshot(delay, callback)
})
.collect();
// Step 3: Batch register
let batch_handle = timer.register_batch(handles, tasks).unwrap();
// Batch cancel
batch_handle.cancel_all();
use kestrel_timer::{TimerWheel, CallbackWrapper, TimerTask};
use std::time::Duration;
let timer = TimerWheel::with_defaults();
// Step 1: Allocate handle and get task_id
let handle = timer.allocate_handle();
let task_id = handle.task_id();
// Step 2: Create and register task
let callback = Some(CallbackWrapper::new(|| async {
println!("Original callback");
}));
let task = TimerTask::new_oneshot(Duration::from_millis(50), callback);
let timer_handle = timer.register(handle, task).unwrap();
// Postpone and keep original callback
timer.postpone(task_id, Duration::from_millis(150), None);
// Postpone and replace callback
let new_callback = Some(CallbackWrapper::new(|| async {
println!("New callback");
}));
timer.postpone(task_id, Duration::from_millis(200), new_callback);
use kestrel_timer::{TimerWheel, TimerService, TimerTask, CallbackWrapper, TaskNotification};
use kestrel_timer::config::ServiceConfig;
use std::time::Duration;
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
// Step 1: Allocate handles
let handles = service.allocate_handles(2);
// Step 2: Create tasks
let tasks: Vec<_> = vec![
TimerTask::new_oneshot(Duration::from_millis(100), Some(CallbackWrapper::new(|| async {}))),
TimerTask::new_oneshot(Duration::from_millis(200), Some(CallbackWrapper::new(|| async {}))),
];
// Step 3: Batch register
service.register_batch(handles, tasks).unwrap();
// Receive timeout notifications
let mut timeout_rx = service.take_receiver().unwrap();
while let Some(notification) = timeout_rx.recv().await {
match notification {
TaskNotification::OneShot(task_id) => {
println!("One-shot task {:?} completed", task_id);
},
TaskNotification::Periodic(task_id) => {
println!("Periodic task {:?} called", task_id);
},
}
}
service.shutdown().await;
βββββββββββββββββββββββββββββββββββββββββββββββ
β L1 Layer (Upper) β
β Slots: 64 | Tick: 1s | Range: 64s β
β β Demote to L0 β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββ
β L0 Layer (Lower) β
β Slots: 512 | Tick: 10ms | Range: 5.12s β
β β² Current Pointer β
βββββββββββββββββββββββββββββββββββββββββββββββ
L0 Layer (Lower - High Precision):
L1 Layer (Upper - Long Duration):
Workflow:
Uses DeferredMap (a generational arena) for efficient task management:
Two-Step Registration:
Generational Safety: Each task ID includes:
Memory Efficiency: Union-based slot storage
parking_lot::Mutex is faster than standard Mutex& (n-1) for fast moduloFull API documentation at docs.rs/kestrel-timer
TimerTask:
TimerTask::new_oneshot(delay, callback) - Create one-shot taskTimerTask::new_periodic(initial_delay, interval, callback, buffer_size) - Create periodic taskget_task_type() - Get task typeget_interval() - Get interval for periodic tasksTaskHandle (Pre-allocated handle):
task_id() - Get task ID from handleTimerWheel:
TimerWheel::with_defaults() - Create with default configTimerWheel::new(config) - Create with custom configallocate_handle() - Allocate single handleallocate_handles(count) - Batch allocate handlesregister(handle, task) - Register task with handleregister_batch(handles, tasks) - Batch register taskscancel(task_id) - Cancel taskcancel_batch(task_ids) - Batch cancelpostpone(task_id, delay, callback) - Postpone taskpostpone_batch(updates) - Batch postponeTimerHandle (Returned after registration):
cancel() - Cancel timertask_id() - Get task IDTimerService:
allocate_handle() - Allocate single handleallocate_handles(count) - Batch allocate handlesregister(handle, task) - Register task with handleregister_batch(handles, tasks) - Batch register taskstake_receiver() - Get timeout notification receivercancel_task(task_id) - Cancel taskcancel_batch(task_ids) - Batch cancelpostpone(task_id, delay, callback) - Postpone taskpostpone_batch(updates) - Batch postponeshutdown() - Shutdown servicelet timer = TimerWheel::with_defaults();
// L0: 512 slots Γ 10ms = 5.12 seconds
// L1: 64 slots Γ 1s = 64 seconds
use kestrel_timer::WheelConfig;
let config = WheelConfig::builder()
.l0_tick_duration(Duration::from_millis(10)) // L0 tick
.l0_slot_count(512) // L0 slots (must be power of 2)
.l1_tick_duration(Duration::from_secs(1)) // L1 tick
.l1_slot_count(64) // L1 slots (must be power of 2)
.build()?;
let timer = TimerWheel::new(config);
High Precision (Network Timeouts):
let config = WheelConfig::builder()
.l0_tick_duration(Duration::from_millis(5))
.l0_slot_count(1024)
.l1_tick_duration(Duration::from_millis(500))
.l1_slot_count(64)
.build()?;
Low Precision (Heartbeat Detection):
let config = WheelConfig::builder()
.l0_tick_duration(Duration::from_millis(100))
.l0_slot_count(512)
.l1_tick_duration(Duration::from_secs(10))
.l1_slot_count(128)
.build()?;
cargo bench
Compared to traditional heap-based (BinaryHeap) timer implementations:
| Operation | Hierarchical Wheel | Heap Implementation | Advantage |
|---|---|---|---|
| Insert Single | O(1) ~5ΞΌs | O(log n) ~10-20ΞΌs | 2-4x faster |
| Batch Insert 1000 | ~2ms | ~15-25ms | 7-12x faster |
| Cancel Task | O(1) ~2ΞΌs | O(n) ~50-100ΞΌs | 25-50x faster |
| Postpone Task | O(1) ~4ΞΌs | O(log n) ~15-30ΞΌs | 4-7x faster |
cargo test --test integration_test test_large_scale_timers
use kestrel_timer::{TimerWheel, CallbackWrapper, TimerTask};
use std::time::Duration;
async fn handle_connection(timer: &TimerWheel, conn_id: u64) {
// Allocate handle first
let handle = timer.allocate_handle();
// Create task
let callback = Some(CallbackWrapper::new(move || async move {
println!("Connection {} timed out", conn_id);
close_connection(conn_id).await;
}));
let task = TimerTask::new_oneshot(Duration::from_secs(30), callback);
// Register task
let timer_handle = timer.register(handle, task).unwrap();
// Cancel timeout when connection completes
// timer_handle.cancel();
}
use kestrel_timer::{TimerWheel, TimerTask, CallbackWrapper};
use kestrel_timer::config::ServiceConfig;
use std::time::Duration;
let timer = TimerWheel::with_defaults();
let mut service = timer.create_service(ServiceConfig::default());
for client_id in client_ids {
// Allocate handle
let handle = service.allocate_handle();
// Create and register task
let callback = Some(CallbackWrapper::new(move || async move {
println!("Client {} heartbeat timeout", client_id);
}));
let task = TimerTask::new_oneshot(Duration::from_secs(30), callback);
service.register(handle, task).unwrap();
}
use kestrel_timer::{TimerTask, CallbackWrapper};
use std::sync::Arc;
use std::time::Duration;
async fn set_cache(&self, key: String, value: String, ttl: Duration) {
self.cache.lock().insert(key.clone(), value);
// Allocate handle
let handle = self.timer.allocate_handle();
// Create task with callback
let cache = Arc::clone(&self.cache);
let callback = Some(CallbackWrapper::new(move || {
let cache = Arc::clone(&cache);
let key = key.clone();
async move {
cache.lock().remove(&key);
}
}));
let task = TimerTask::new_oneshot(ttl, callback);
// Register task
self.timer.register(handle, task).unwrap();
}
use kestrel_timer::{TimerWheel, TimerTask, CallbackWrapper, TaskId};
use std::time::Duration;
async fn apply_buff(
timer: &TimerWheel,
player_id: u64,
buff_type: BuffType,
duration: Duration
) -> TaskId {
// Allocate handle and get task_id
let handle = timer.allocate_handle();
let task_id = handle.task_id();
// Create and register task
let callback = Some(CallbackWrapper::new(move || async move {
remove_buff(player_id, buff_type).await;
}));
let task = TimerTask::new_oneshot(duration, callback);
timer.register(handle, task).unwrap();
task_id
}
// Extend buff duration
timer.postpone(task_id, new_duration, None);
use kestrel_timer::{TimerWheel, TimerTask, CallbackWrapper};
use std::time::Duration;
async fn retry_with_backoff(timer: &TimerWheel, operation: impl Fn()) {
for retry in 1..=5 {
let delay = Duration::from_secs(2_u64.pow(retry - 1));
// Allocate handle
let handle = timer.allocate_handle();
// Create and register task
let callback = Some(CallbackWrapper::new(move || async move {
operation().await;
}));
let task = TimerTask::new_oneshot(delay, callback);
timer.register(handle, task).unwrap();
}
}
This project is licensed under either of:
The timing wheel algorithm was first proposed by George Varghese and Tony Lauck in the paper "Hashed and Hierarchical Timing Wheels" (SOSP '87).
Full Documentation: docs.rs/kestrel-timer