github.com/ngicks/gokugen@v0.0.5/scheduler/dispatch_loop.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/ngicks/gokugen/common"
     8  )
     9  
    10  // DispatchLoop waits for a TaskTimer to emit the timer signal,
    11  // and then sends scheduled tasks to worker channel.
    12  //
    13  // Multiple calls of Start is allowed. But performance benefits are questionable.
    14  type DispatchLoop struct {
    15  	taskTimer *TaskTimer
    16  	getNow    common.GetNower
    17  }
    18  
    19  // NewDispatchLoop creates DispatchLoop.
    20  //
    21  // panic: when one or more of arguments is nil.
    22  func NewDispatchLoop(taskTimer *TaskTimer, getNow common.GetNower) *DispatchLoop {
    23  	if taskTimer == nil || getNow == nil {
    24  		panic(fmt.Errorf(
    25  			"%w: one or more of aruguments is nil. taskTimer is nil=[%t], getNow is nil=[%t]",
    26  			ErrInvalidArg,
    27  			taskTimer == nil,
    28  			getNow == nil,
    29  		))
    30  	}
    31  	return &DispatchLoop{
    32  		taskTimer: taskTimer,
    33  		getNow:    getNow,
    34  	}
    35  }
    36  
    37  func (l *DispatchLoop) PushTask(task *Task) error {
    38  	return l.taskTimer.Push(task)
    39  }
    40  
    41  func (l *DispatchLoop) TaskLen() int {
    42  	return l.taskTimer.Len()
    43  }
    44  
    45  // Start starts a dispatch loop.
    46  // Start does not have reponsibility of starting the TaskTimer.
    47  // A caller must ensure that the taskTimer is started.
    48  // Calling multiple Start in different goroutines is allowed, but performance benefits are questionable.
    49  // Cancelling ctx will end this Start loop, with returning nil.
    50  //
    51  // If one or more of arguments are nil, Start immediately returns ErrInvalidArg.
    52  //
    53  // panic: Closing taskCh *before* cancelling ctx may cause panic.
    54  func (l *DispatchLoop) Start(ctx context.Context, taskCh chan<- *Task) error {
    55  	if taskCh == nil || ctx == nil {
    56  		return ErrInvalidArg
    57  	}
    58  
    59  loop:
    60  	for {
    61  		select {
    62  		case <-ctx.Done():
    63  			break loop
    64  		case <-l.taskTimer.GetTimer():
    65  			next := l.taskTimer.GetScheduledTask(l.getNow.GetNow())
    66  			if next == nil {
    67  				continue
    68  			}
    69  			for _, w := range next {
    70  				if ctx.Err() != nil {
    71  					// race condition causes deadlock here.
    72  					// must not send in that case.
    73  					l.taskTimer.Push(w, false)
    74  				} else {
    75  					taskCh <- w
    76  				}
    77  			}
    78  		}
    79  	}
    80  	return nil
    81  }