Documentation
¶
Overview ¶
Package cherryTimeWheel implements a 5-level hierarchical timer wheel.
Architecture:
- 512 slots (256 near + 4×64 levels)
- Single driver goroutine, lock-free
- MPSC submission via timerCmd interface
- timerNode is owned 1:1 by its Timer handle (no object pool; a node lives as long as its handle and is reclaimed by GC)
- Dispatch runs synchronously in the driver goroutine and only fires timers; callbacks must be lightweight (business logic is pushed to actor queues by the upper layer, not executed inside the wheel)
- A timer armed via SetNext recomputes its next delay after every fire; a non-positive delay stops it (dynamic interval mode)
Concurrency:
- Start/Stop are lifecycle methods and MUST be called serially from a single goroutine (the caller serializes them); they are not synchronized internally.
- submitAdd/submitRemove are safe to call from any number of goroutines concurrently.
- A *Timer handle is NOT goroutine-safe; drive it from the single goroutine that created it.
- Timer.Stop is immediate: the node's running flag is cleared atomically, so no new callback starts after Stop returns (a callback already executing is not interrupted). A remove command is still queued to reclaim the node.
- timerNode / nodeMap / wheel slots are owned by the driver goroutine and are only touched there (the atomic running flag is also written by Stop).
Cascade (bitwise, branch-free):
near (256t): current & 0xFF == 0 → cascade(0) t[0] (16Kt): current & 0x3FFF == 0 → cascade(1) t[1] (1Mt): current & 0xFFFFF == 0 → cascade(2) t[2] (67Mt): current & 0x3FFFFFF == 0 → cascade(3)
Limitations:
- Fixed 5 levels cap the maximum expressible delay at 2^32 ticks (~497 days at the 10ms default tick, ~49 days at 1ms). Longer delays wrap to a wrong slot and fire early, so keep delays within this ceiling.
- The wheel advances on a fixed ticker instead of sleeping until the next expiry, so it burns a little CPU even when idle (negligible at 10ms).
- submitAdd/submitRemove use a bounded MPSC buffer; AddTimer/RemoveTimer block under backpressure, which only happens if the driver goroutine stalls.
- Stop is terminal: once stopped, the wheel is closed and cannot be restarted (Start becomes a no-op); create a new wheel instead.
Index ¶
- Constants
- type EverySchedule
- type FixedDateSchedule
- type Scheduler
- type TimeWheel
- func (tw *TimeWheel) ActiveCount() int64
- func (tw *TimeWheel) AddScheduleTimer(s Scheduler, f func()) *Timer
- func (tw *TimeWheel) AddTimer(d time.Duration, f func(), once bool) *Timer
- func (tw *TimeWheel) NewScheduleTimer(s Scheduler, f func()) *Timer
- func (tw *TimeWheel) NewTimer(d time.Duration, f func(), once bool) *Timer
- func (tw *TimeWheel) RemoveTimer(id uint64)
- func (tw *TimeWheel) Start()
- func (tw *TimeWheel) Stop()
- type Timer
Constants ¶
const ( NEAR_SHIFT = 8 // 2^8 = 256 slots NEAR_SIZE = 1 << NEAR_SHIFT // 256 LEVEL_SHIFT = 6 // 2^6 = 64 slots per level LEVEL_SIZE = 1 << LEVEL_SHIFT // 64 NEAR_MASK = NEAR_SIZE - 1 // 0xFF LEVEL_MASK = LEVEL_SIZE - 1 // 0x3F SUBMIT_CAP = 4096 // MPSC submit channel buffer DefaultTick = 10 * time.Millisecond // minimum tick )
5-level timer wheel geometry
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EverySchedule ¶
type FixedDateSchedule ¶
type FixedDateSchedule struct {
Hour, Minute, Second int
}
type Scheduler ¶
type Scheduler interface {
// Next returns the next execution time after the given (previous) time.
// It will return a zero time if no next time is scheduled.
//
// Times use the local clock (AddScheduleTimer seeds with time.Now()).
Next(time.Time) time.Time
}
Scheduler determines the execution plan of a task.
type TimeWheel ¶
type TimeWheel struct {
// contains filtered or unexported fields
}
TimeWheel is a 5-level hierarchical timer wheel. Wheel advance is serialized in a single driver goroutine; command submission (submitAdd/submitRemove) is concurrent from many goroutines.
func NewTimeWheel ¶
NewTimeWheel creates a timer wheel instance.
func NewTimeWheelWithHint ¶ added in v1.6.0
NewTimeWheelWithHint creates a timer wheel instance with a pre-allocated nodeMap capacity hint (recommended for million-scale workloads).
func (*TimeWheel) ActiveCount ¶ added in v1.6.0
ActiveCount returns the number of active timers.
func (*TimeWheel) AddScheduleTimer ¶ added in v1.6.0
AddScheduleTimer creates a Scheduler-driven timer and starts it immediately. It returns nil when the scheduler reports no next expiry.
func (*TimeWheel) AddTimer ¶ added in v1.6.0
AddTimer creates a timer and starts it immediately. A one-shot timer (once=true) fires a single time and then finishes; a recurring timer fires repeatedly at the given interval.
func (*TimeWheel) NewScheduleTimer ¶ added in v1.6.1
NewScheduleTimer creates a Scheduler-driven timer without starting it. Call Start to compute the first expiry and submit it to the driver.
func (*TimeWheel) NewTimer ¶ added in v1.6.1
NewTimer creates a timer bound to the wheel without starting it. Call Start to submit it to the driver. A one-shot timer (once=true) fires once; a recurring timer fires at a fixed interval.
func (*TimeWheel) RemoveTimer ¶ added in v1.6.0
RemoveTimer stops the timer with the given id. Asynchronous: the removal is queued to the driver, so a callback already dispatching may still fire once. Use Timer.Stop for an immediate cancel via the handle.
type Timer ¶
type Timer struct {
// contains filtered or unexported fields
}
Timer is a user-facing handle with Start/Stop/Clear. It is NOT goroutine-safe: a Timer must be driven from a single goroutine (the owner that created it). The underlying *timerNode is owned by the wheel's driver goroutine and only mutated through the command channel.
func (*Timer) Clear ¶ added in v1.6.0
func (t *Timer) Clear()
Clear stops the timer and releases references for safe reuse.
func (*Timer) IsOnce ¶ added in v1.6.0
IsOnce reports whether the timer is one-shot (fires once, then finishes).
func (*Timer) IsRunning ¶ added in v1.6.0
IsRunning reports whether the timer is active (added/started and not yet stopped or completed).
func (*Timer) SetNext ¶ added in v1.6.1
SetNext sets or replaces the dynamic next-delay callback.
For a recurring timer, fn is invoked by the driver after every fire to compute the delay until the next fire; a non-positive return stops the timer. The first fire always uses the timer's original delay d. Setting fn takes effect immediately: the already-queued next fire is re-armed.
For a one-shot timer, fn computes the single-fire delay on the next Start; without one the timer fires at its original delay d. The timer still fires exactly once, then stops.
nil clears dynamic mode, reverting to the timer's original behavior (fixed interval or one-shot). A schedule-driven timer (created via AddScheduleTimer) is a separate system and rejects any SetNext call: it is ignored with a warning. SetNext must be called from the timer's owner goroutine (the same goroutine that drives Start/Stop).
func (*Timer) Start ¶ added in v1.6.0
func (t *Timer) Start()
Start submits the timer to the wheel and starts it, keeping its original type: a one-shot timer stays one-shot, a recurring timer stays recurring. A schedule-driven timer recomputes its first expiry from the scheduler; if the scheduler reports no next expiry, nothing is scheduled.
func (*Timer) Stop ¶
func (t *Timer) Stop()
Stop cancels the timer immediately: the running flag is cleared atomically, so no new callback starts after Stop returns. A remove command is queued to detach and reset the node promptly. A callback already executing at the moment Stop is called cannot be interrupted. Stop clears the node immediately, so a subsequent Start builds a fresh node.