Kestrel Timer
High-performance async timer library based on Hierarchical Timing Wheel algorithm
📚 Table of Contents
- Overview
- Key Features
- Quick Start
- Architecture
- Usage Examples
- Configuration
- Benchmarks
- Use Cases
- License
Overview
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:
- Hierarchical timing wheel architecture that automatically separates short-delay and long-delay tasks
- 2-12x performance improvement over traditional heap-based implementations
- Support for timer postponement, batch operations, and completion notifications
- Production-ready with comprehensive testing
Key Features
🏗️ Hierarchical Timing Wheel
- Dual-layer design: L0 layer (high-precision short-delay) + L1 layer (long-delay) with automatic layering
- Smart demotion: L1 tasks automatically demote to L0 layer when due
- No round checks: L0 layer eliminates rounds checking for 90% of tasks
⚡ High Performance
- O(1) time complexity: Insert, delete, and trigger operations are all O(1)
- Optimized data structures:
DeferredMap(generational arena) for task indexing with O(1) operationsparking_lot::Mutexfor efficient locking
- Bitwise optimization: Slot count is power of 2 for fast modulo operations
- Large-scale support: Easily handles 10,000+ concurrent timers
🔄 Complete Features
- ✅ Async callback support (based on Tokio)
- ✅ Timer postponement (keep or replace callback)
- ✅ Batch operations (schedule, cancel, postpone)
- ✅ Completion notification mechanism
- ✅ TimerService (Actor-based management)
- ✅ Thread-safe
Quick Start
Installation
Add to your Cargo.toml:
[]
= "0.2.0"
= { = "1.48", = ["full"] }
Basic Usage
use ;
use Duration;
async
Batch Operations
use ;
use Duration;
let timer = with_defaults;
// Step 1: Batch allocate handles
let handles = timer.allocate_handles;
let task_ids: = handles.iter.map.collect;
// Step 2: Create tasks
let tasks: =
.map
.collect;
// Step 3: Batch register
let batch_handle = timer.register_batch.unwrap;
// Batch cancel
batch_handle.cancel_all;
Postpone Timer
use ;
use Duration;
let timer = 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;
let task = new_oneshot;
let timer_handle = timer.register.unwrap;
// Postpone and keep original callback
timer.postpone;
// Postpone and replace callback
let new_callback = Some;
timer.postpone;
TimerService Usage
use ;
use ServiceConfig;
use Duration;
let timer = with_defaults;
let mut service = timer.create_service;
// Step 1: Allocate handles
let handles = service.allocate_handles;
// Step 2: Create tasks
let tasks: = vec!;
// Step 3: Batch register
service.register_batch.unwrap;
// Receive timeout notifications
let mut timeout_rx = service.take_receiver.unwrap;
while let Some = timeout_rx.recv.await
service.shutdown.await;
Architecture
Hierarchical Timing Wheel Design
┌─────────────────────────────────────────────┐
│ 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):
- Slots: 512 (default), Tick: 10ms
- Coverage: 5.12 seconds
- Handles 80-90% of short-delay tasks
L1 Layer (Upper - Long Duration):
- Slots: 64 (default), Tick: 1000ms
- Coverage: 64 seconds
- Handles long-delay tasks with rounds mechanism
Workflow:
- Short delay (< 5.12s) → Insert directly into L0 layer
- Long delay (≥ 5.12s) → Insert into L1 layer
- L1 task due → Automatically demote to L0 layer
- L0 task due → Trigger immediately
Task Indexing with DeferredMap
Uses DeferredMap (a generational arena) for efficient task management:
-
Two-Step Registration:
- Allocate handle to get task ID (cheap, no value needed)
- Insert task using the handle (with completion notifiers)
-
Generational Safety: Each task ID includes:
- Lower 32 bits: Slot index
- Upper 32 bits: Generation counter
- Prevents use-after-free and ABA problems
-
Memory Efficiency: Union-based slot storage
- Occupied slots: Store task data
- Vacant slots: Store free-list pointer
Performance Optimizations
- Hierarchical architecture: Avoids single-layer round checks, L0 layer requires no rounds checking
- DeferredMap: O(1) task lookup, insertion, and removal with generational safety
- Efficient locking:
parking_lot::Mutexis faster than standard Mutex - Bitwise optimization: Slot count is power of 2, uses
& (n-1)for fast modulo - Cache optimization: Pre-compute slot masks, tick durations, and other frequently used values
- Batch optimization: Reduce lock contention with smart small-batch handling
Usage Examples
Full API documentation at docs.rs/kestrel-timer
Main APIs
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 tasks
TaskHandle (Pre-allocated handle):
task_id()- Get task ID from handle
TimerWheel:
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 postpone
TimerHandle (Returned after registration):
cancel()- Cancel timertask_id()- Get task ID
TimerService:
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 service
Configuration
Default Configuration
let timer = with_defaults;
// L0: 512 slots × 10ms = 5.12 seconds
// L1: 64 slots × 1s = 64 seconds
Custom Configuration
use WheelConfig;
let config = builder
.l0_tick_duration // L0 tick
.l0_slot_count // L0 slots (must be power of 2)
.l1_tick_duration // L1 tick
.l1_slot_count // L1 slots (must be power of 2)
.build?;
let timer = new;
Recommended Configurations
High Precision (Network Timeouts):
let config = builder
.l0_tick_duration
.l0_slot_count
.l1_tick_duration
.l1_slot_count
.build?;
Low Precision (Heartbeat Detection):
let config = builder
.l0_tick_duration
.l0_slot_count
.l1_tick_duration
.l1_slot_count
.build?;
Benchmarks
Run Benchmarks
Performance Comparison
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 |
Large Scale Testing
- ✅ 10,000 concurrent timers
- ✅ Creation time < 100ms
- ✅ All timers fire correctly
Use Cases
1. Network Timeout Management
use ;
use Duration;
async
2. Heartbeat Detection
use ;
use ServiceConfig;
use Duration;
let timer = with_defaults;
let mut service = timer.create_service;
for client_id in client_ids
3. Cache Expiration
use ;
use Arc;
use Duration;
async
4. Game Buff System
use ;
use Duration;
async
// Extend buff duration
timer.postpone;
5. Retry Mechanism
use ;
use Duration;
async
License
This project is licensed under either of:
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
Acknowledgments
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