cherryTimeWheel

package
v1.6.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 5 Imported by: 1

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

View Source
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 EverySchedule struct {
	Interval time.Duration
}

func (*EverySchedule) Next

func (s *EverySchedule) Next(prev time.Time) time.Time

type FixedDateSchedule

type FixedDateSchedule struct {
	Hour, Minute, Second int
}

func (*FixedDateSchedule) Next

func (s *FixedDateSchedule) Next(prev time.Time) time.Time

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

func NewTimeWheel(tick time.Duration) *TimeWheel

NewTimeWheel creates a timer wheel instance.

func NewTimeWheelWithHint added in v1.6.0

func NewTimeWheelWithHint(tick time.Duration, hint int) *TimeWheel

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

func (tw *TimeWheel) ActiveCount() int64

ActiveCount returns the number of active timers.

func (*TimeWheel) AddScheduleTimer added in v1.6.0

func (tw *TimeWheel) AddScheduleTimer(s Scheduler, f func()) *Timer

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

func (tw *TimeWheel) AddTimer(d time.Duration, f func(), once bool) *Timer

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

func (tw *TimeWheel) NewScheduleTimer(s Scheduler, f func()) *Timer

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

func (tw *TimeWheel) NewTimer(d time.Duration, f func(), once bool) *Timer

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

func (tw *TimeWheel) RemoveTimer(id uint64)

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.

func (*TimeWheel) Start

func (tw *TimeWheel) Start()

Start begins the driver goroutine. Idempotent. Must not be called concurrently with Stop (lifecycle is serialized by the caller).

func (*TimeWheel) Stop

func (tw *TimeWheel) Stop()

Stop shuts down the driver goroutine. Idempotent and terminal: once stopped the wheel is closed and cannot be restarted. Must not be called concurrently with Start (lifecycle is serialized by the caller).

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) ID

func (t *Timer) ID() uint64

ID returns the timer's unique ID.

func (*Timer) IsOnce added in v1.6.0

func (t *Timer) IsOnce() bool

IsOnce reports whether the timer is one-shot (fires once, then finishes).

func (*Timer) IsRunning added in v1.6.0

func (t *Timer) IsRunning() bool

IsRunning reports whether the timer is active (added/started and not yet stopped or completed).

func (*Timer) SetNext added in v1.6.1

func (t *Timer) SetNext(fn func() time.Duration)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL