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  }