github.com/benz9527/toy-box/algo@v0.0.0-20240221120937-66c0c6bd5abd/timer/timing_wheel_intf.go (about)

     1  package timer
     2  
     3  // Ref:
     4  // https://dl.acm.org/doi/10.1145/41457.37504
     5  // https://ieeexplore.ieee.org/document/650142
     6  // github ref:
     7  // https://github.com/apache/kafka/tree/trunk/server-common/src/main/java/org/apache/kafka/server/util/timer
     8  // https://github.com/RussellLuo/timingwheel (Not a good implementation)
     9  
    10  import (
    11  	"context"
    12  	"errors"
    13  	"time"
    14  )
    15  
    16  var (
    17  	ErrTimingWheelStopped                   = errors.New("timing wheel stopped")
    18  	ErrTimingWheelTaskNotFound              = errors.New("task not found")
    19  	ErrTimingWheelTaskEmptyJobID            = errors.New("empty job id in task")
    20  	ErrTimingWheelEmptyJob                  = errors.New("empty job in task")
    21  	ErrTimingWheelTaskIsExpired             = errors.New("task is expired")
    22  	ErrTimingWheelTaskUnableToBeAddedToSlot = errors.New("task unable to be added to slot")
    23  	ErrTimingWheelTaskUnableToBeRemoved     = errors.New("task unable to be removed")
    24  	ErrTimingWheelTaskTooShortExpiration    = errors.New("task expiration is too short")
    25  	ErrTimingWheelUnknownScheduler          = errors.New("unknown schedule")
    26  	ErrTimingWheelTaskCancelled             = errors.New("task cancelled")
    27  )
    28  
    29  type TimingWheelCommonMetadata interface {
    30  	// GetTickMs returns the baseline tick ms (interval) of the timing wheel.
    31  	GetTickMs() int64
    32  	// GetSlotSize returns the slot size of the timing wheel.
    33  	GetSlotSize() int64
    34  	// GetStartMs returns the start ms of the timing wheel.
    35  	GetStartMs() int64
    36  }
    37  
    38  // TimingWheel slots is private,
    39  // they should be provided by the implementation
    40  type TimingWheel interface {
    41  	TimingWheelCommonMetadata
    42  	GetInterval() int64
    43  	GetCurrentTimeMs() int64
    44  }
    45  
    46  type Scheduler interface {
    47  	// next returns the next expiredMs.
    48  	next(beginMs int64) (nextExpiredMs int64)
    49  	// GetRestLoopCount returns the rest loop count.
    50  	// If the rest loop count is -1, it means that the task will run forever unless cancel manually.
    51  	GetRestLoopCount() int64
    52  }
    53  
    54  type TimingWheels interface {
    55  	TimingWheelCommonMetadata
    56  	// GetTaskCounter returns the current task count of the timing wheel.
    57  	GetTaskCounter() int64
    58  	// AddTask adds a task to the timing wheels.
    59  	AddTask(task Task) error
    60  	// CancelTask cancels a task by jobID.
    61  	CancelTask(jobID JobID) error
    62  	// Shutdown stops the timing wheels
    63  	Shutdown()
    64  	// AfterFunc schedules a function to run after the duration delayMs.
    65  	AfterFunc(delayMs time.Duration, fn Job) (Task, error)
    66  	// ScheduleFunc schedules a function to run at a certain time generated by the schedule.
    67  	ScheduleFunc(schedFn func() Scheduler, fn Job) (Task, error)
    68  }
    69  
    70  // JobID is the unique identifier of a job
    71  type JobID string
    72  
    73  // Job is the function that will be executed by the timing wheel
    74  type Job func(ctx context.Context, metadata JobMetadata)
    75  
    76  type JobType uint8
    77  
    78  const (
    79  	OnceJob JobType = iota
    80  	RepeatedJob
    81  )
    82  
    83  func (t JobType) String() string {
    84  	switch t {
    85  	case OnceJob:
    86  		return "once"
    87  	case RepeatedJob:
    88  		return "repeat"
    89  	default:
    90  	}
    91  	return "unknown"
    92  }
    93  
    94  // All metadata interfaces are designed for debugging and monitoring friendly.
    95  
    96  // JobMetadata describes the metadata of a job
    97  // Each slot in the timing wheel is a linked list of jobs
    98  type JobMetadata interface {
    99  	// GetJobID returns the jobID of the job, unique identifier.
   100  	GetJobID() JobID
   101  	// GetExpiredMs returns the expirationMs of the job.
   102  	GetExpiredMs() int64
   103  	// GetRestLoopCount returns the rest loop count.
   104  	GetRestLoopCount() int64
   105  	// GetJobType returns the job type.
   106  	GetJobType() JobType
   107  }
   108  
   109  // Task is the interface that wraps the Job
   110  type Task interface {
   111  	JobMetadata
   112  	GetJobMetadata() JobMetadata
   113  	// GetJob returns the job function.
   114  	GetJob() Job
   115  	// GetSlot returns the slot of the job.
   116  	GetSlot() TimingWheelSlot
   117  	// setSlot sets the slot of the job, it is a private method.
   118  	setSlot(slot TimingWheelSlot)
   119  	// GetPreviousSlotMetadata returns the previous slot metadata of the job.
   120  	GetPreviousSlotMetadata() TimingWheelSlotMetadata
   121  	// setPreviousSlotMetadata sets the current slot metadata of the job.
   122  	setSlotMetadata(slotMetadata TimingWheelSlotMetadata)
   123  	Cancel() bool
   124  	Cancelled() bool
   125  }
   126  
   127  // ScheduledTask is the interface that wraps the repeat Job
   128  type ScheduledTask interface {
   129  	Task
   130  	UpdateNextScheduledMs()
   131  }
   132  
   133  // TaskHandler is a function that reinserts a task into the timing wheel.
   134  // It means that the task should be executed periodically or repeatedly for a certain times.
   135  // Reinsert will add current task to next slot, higher level slot (overflow wheel) or
   136  // the same level slot (current wheel) depending on the expirationMs of the task.
   137  // When the task is reinserted, the expirationMs of the task should be updated.
   138  //  1. Check if the task is cancelled. If so, stop reinserting.
   139  //  2. Check if the task's loop count is greater than 0. If so, decrease the loop count and reinsert.
   140  //  3. Check if the task's loop count is -1 (run forever unless cancel manually).
   141  //     If so, reinsert and update the expirationMs.
   142  type TaskHandler func(Task) // Core function
   143  
   144  type TimingWheelSlotMetadata interface {
   145  	// GetExpirationMs returns the expirationMs of the slot.
   146  	GetExpirationMs() int64
   147  	// setExpirationMs sets the expirationMs of the slot.
   148  	setExpirationMs(expirationMs int64) bool
   149  	// GetSlotID returns the slotID of the slot, easy for debugging.
   150  	GetSlotID() int64
   151  	// setSlotID sets the slotID of the slot, easy for debugging.
   152  	setSlotID(slotID int64)
   153  	// GetLevel returns the level of the slot, easy for debugging.
   154  	GetLevel() int64
   155  	// setLevel sets the level of the slot, easy for debugging.
   156  	setLevel(level int64)
   157  }
   158  
   159  // TimingWheelSlot is the interface that wraps the slot, in kafka, it is called bucket.
   160  type TimingWheelSlot interface {
   161  	TimingWheelSlotMetadata
   162  	// GetMetadata returns the metadata of the slot.
   163  	GetMetadata() TimingWheelSlotMetadata
   164  	// AddTask adds a task to the slot.
   165  	AddTask(Task)
   166  	// RemoveTask removes a task from the slot.
   167  	RemoveTask(Task) bool
   168  	// Flush flushes all tasks in the slot generally,
   169  	// but it should be called in a loop.
   170  	Flush(TaskHandler)
   171  }