github.com/ngicks/gokugen@v0.0.5/scheduler/task_timer.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 8 "github.com/ngicks/gokugen/common" 9 ) 10 11 // TaskTimer is a wrapper of a min-heap and a timer channel. 12 // It manages timer to be always reset to a min task. 13 type TaskTimer struct { 14 workingState 15 mu sync.Mutex 16 q TaskQueue 17 getNow common.GetNower 18 timer common.ITimer 19 } 20 21 // NewTaskTimer creates TaskTimer. 22 // queueMax is max for tasks. Passing zero sets it unlimited. 23 // 24 // panic: If getNow or timerImpl is nil. 25 func NewTaskTimer(queueMax uint, getNow common.GetNower, timerImpl common.ITimer) *TaskTimer { 26 if getNow == nil || timerImpl == nil { 27 panic( 28 fmt.Errorf( 29 "%w: one or more of aruguments is nil. getNow is nil=[%t], timerImpl is nil=[%t]", 30 ErrInvalidArg, 31 getNow == nil, 32 timerImpl == nil, 33 ), 34 ) 35 } 36 return &TaskTimer{ 37 q: NewUnsafeQueue(queueMax), 38 getNow: getNow, 39 timer: timerImpl, 40 } 41 } 42 43 // Start starts TaskTimer, 44 // setting timer to min task if any. 45 func (f *TaskTimer) Start() { 46 f.mu.Lock() 47 defer f.mu.Unlock() 48 if min := f.q.Peek(); min != nil { 49 f.timer.Reset(min.scheduledTime, f.getNow.GetNow()) 50 } 51 } 52 53 func (f *TaskTimer) Stop() { 54 f.setWorking(false) 55 f.timer.Stop() 56 } 57 58 // GetTimer returns timer channel 59 // that emits when a scheduled time of a min task is past. 60 func (f *TaskTimer) GetTimer() <-chan time.Time { 61 return f.timer.GetChan() 62 } 63 64 // Push pushes *Task into underlying heap. 65 // After successful push, timer is updated to new min element 66 // unless one of shouldResetTimer is false. 67 func (f *TaskTimer) Push(task *Task, shouldResetTimer ...bool) error { 68 shouldReset := true 69 for _, reset := range shouldResetTimer { 70 if !reset { 71 shouldReset = false 72 } 73 } 74 f.mu.Lock() 75 defer f.mu.Unlock() 76 77 var prevMin *Task 78 if shouldReset { 79 prevMin = f.q.Peek() 80 } 81 82 err := f.q.Push(task) 83 if err != nil { 84 return err 85 } 86 87 if shouldReset { 88 if newMin := f.q.Peek(); prevMin == nil || newMin.scheduledTime.Before(prevMin.scheduledTime) { 89 f.timer.Reset(newMin.scheduledTime, f.getNow.GetNow()) 90 } 91 } 92 return nil 93 } 94 95 func excludeCancelled(ele *Task) bool { 96 if ele == nil { 97 return false 98 } 99 return ele.IsCancelled() 100 } 101 102 // RemoveCancelled removes elements from underlying heap if they are cancelled. 103 // Heap is scanned in given range, [start,end). 104 // Wider the range is, longer it may hold lock. So range size must be chosen wisely. 105 func (f *TaskTimer) RemoveCancelled(start, end int) (removed []*Task) { 106 f.mu.Lock() 107 defer f.mu.Unlock() 108 return f.q.Exclude(excludeCancelled, start, end) 109 } 110 111 func (f *TaskTimer) Len() int { 112 f.mu.Lock() 113 defer f.mu.Unlock() 114 return f.q.Len() 115 } 116 117 // GetScheduledTask gets tasks whose scheduled time is earlier than given time t. 118 // Note that returned slice might be empty. Because min task might be removed in racy `RemoveCancelled` call. 119 func (f *TaskTimer) GetScheduledTask(t time.Time) []*Task { 120 f.mu.Lock() 121 defer f.mu.Unlock() 122 tasks := make([]*Task, 0) 123 for { 124 p := f.q.Pop(func(next *Task) bool { 125 if next == nil { 126 return false 127 } 128 return next.scheduledTime.Before(t) || next.scheduledTime.Equal(t) 129 }) 130 if p == nil { 131 break 132 } 133 tasks = append(tasks, p) 134 } 135 136 if p := f.q.Peek(); p != nil { 137 f.timer.Reset(p.scheduledTime, f.getNow.GetNow()) 138 } 139 return tasks 140 } 141 142 // Peek return min element without modifying underlying heap. 143 func (f *TaskTimer) Peek() *Task { 144 f.mu.Lock() 145 defer f.mu.Unlock() 146 return f.q.Peek() 147 }