timingwheel

repository·master·Indexed 20 days ago

https://github.com/russellluo/timingwheel

A high-performance Golang implementation of Hierarchical Timing Wheels inspired by Kafka's purgatory design. It provides efficient timer management for large numbers of timers using multiple layers of wheels, featuring support for one-off tasks via AfterFunc and recurring tasks via ScheduleFunc and the Scheduler interface.

Tokens
1.5K
Snippets
7
Records
8
Agent score
22%

What's inside timingwheel

  1. What are Hierarchical Timing Wheels?

    master
    The timingwheel library is a Golang implementation of Hierarchical Timing Wheels, a design ported from Kafka's purgatory. This architecture is designed to manage large numbers of timers efficiently by using multiple layers of wheels representing different time granularities.
  2. Initialize a TimingWheel with NewTimingWheel

    master

    Use NewTimingWheel to create a new hierarchical timing wheel instance. You must provide a tick duration (the granularity of the wheel) and a wheelSize (the number of buckets in the wheel).

    Constraints:

    • The tick must be at least 1ms. If a duration less than 1ms is provided, the function will panic.
    • The wheelSize determines the total interval covered by a single wheel level before it overflows into a higher-level wheel.
    // tick: 10ms granularity, wheelSize: 60 buckets (covers 600ms per level)
    tw := timingwheel.NewTimingWheel(10*time.Millisecond, 60)
  3. Schedule recurring tasks with ScheduleFunc

    master

    Use ScheduleFunc to execute a function f repeatedly according to a custom execution plan defined by a Scheduler implementation.

    How it works:

    1. The Scheduler's Next(time.Time) method is called to determine the first execution time.
    2. When the task executes, it automatically calculates the next execution time using s.Next() and re-schedules itself.
    3. If s.Next() returns a zero time, the recurring cycle stops.

    It returns a *Timer which can be used to stop the entire recurring schedule.

    type MyScheduler struct{}
    func (s *MyScheduler) Next(t time.Time) time.Time {
        return t.Add(1 * time.Minute) // Run every minute
    }
    
    tw := timingwheel.NewTimingWheel(10*time.Millisecond, 60)
    tw.Start()
    
    sched := &MyScheduler{}
    timer := tw.ScheduleFunc(sched, func() {
        fmt.Println("Recurring task running")
    })
    
    // To stop the recurring schedule:
    // timer.Stop()
  4. Schedule a one-off task with AfterFunc

    master

    Use AfterFunc to execute a function f after a specific duration d has elapsed. The function is executed in its own goroutine, similar to time.AfterFunc in the standard library.

    It returns a *Timer object, which allows you to cancel the scheduled task by calling t.Stop() before it executes.

    tw := timingwheel.NewTimingWheel(10*time.Millisecond, 60)
    tw.Start()
    
    timer := tw.AfterFunc(5*time.Second, func() {
        fmt.Println("Task executed after 5 seconds")
    })
    
    // To cancel the task:
    // timer.Stop()
  5. Start and Stop the TimingWheel

    master

    A TimingWheel must be explicitly started to process timers.

    • Start(): Begins the background processing of the timing wheel. It uses a delay queue to poll for expired buckets and advances the clock.
    • Stop(): Signals the timing wheel to stop. It closes the internal exit channel and waits for the background goroutines to finish.

    Note: Stop() does not wait for individual timer tasks (the functions passed to AfterFunc or ScheduleFunc) to complete. If you need to ensure tasks are finished, you must implement your own coordination mechanism.

    tw := timingwheel.NewTimingWheel(10*time.Millisecond, 60)
    tw.Start()
    
    // ... use the wheel ...
    
    tw.Stop()
  6. Implement the Scheduler interface

    master

    To use ScheduleFunc, you must provide an implementation of the Scheduler interface. This interface defines the logic for calculating when a task should run next.

    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.
        // All times must be UTC.
        Next(time.Time) time.Time
    }
    type MyScheduler struct{}
    
    func (s *MyScheduler) Next(t time.Time) time.Time {
        // Logic to determine the next run time
        return t.Add(time.Second)
    }
  7. Manage timer lifecycle with Timer

    master

    The Timer type represents a single event in the timing wheel. When the timer expires, the associated task function is executed.

    You can use Stop() to prevent a timer from firing. Stop() returns true if the timer was successfully stopped, and false if it has already expired or was already stopped.

    Note on Concurrency: If the timer has already expired and the task has started in its own goroutine, Stop() does not wait for the task to complete. If you need to ensure the task is finished, you must implement your own coordination mechanism.

    // Example concept of a Timer (Note: Timer is typically managed by the timing wheel)
    timer := &timingwheel.Timer{
        task: func() {
            fmt.Println("Timer expired!")
        },
    }
    
    // Stop the timer
    if stopped := timer.Stop(); stopped {
        fmt.Println("Timer stopped successfully")
    } else {
        fmt.Println("Timer could not be stopped (already expired or stopped)")
    }