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

     1  package scheduler
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/ngicks/gokugen/common"
     9  )
    10  
    11  // CancellerLoop requests TaskTimer to remove cancelled element at a given interval.
    12  // This is intended to be only one running instance per a TaskTimer.
    13  type CancellerLoop struct {
    14  	workingState
    15  	taskTimer *TaskTimer
    16  	getNow    common.GetNower
    17  	interval  time.Duration
    18  }
    19  
    20  // NewCancellerLoop creates a CancellerLoop.
    21  //
    22  // panic: when one or more of arguments is nil or zero-vakue.
    23  func NewCancellerLoop(taskTimer *TaskTimer, getNow common.GetNower, interval time.Duration) *CancellerLoop {
    24  	if taskTimer == nil || getNow == nil || interval <= 0 {
    25  		panic(fmt.Errorf(
    26  			"%w: one or more of aruguments is nil or zero-value. taskTimer is nil=[%t], getNow is nil=[%t], interval is zero=[%t]",
    27  			ErrInvalidArg,
    28  			taskTimer == nil,
    29  			getNow == nil,
    30  			interval == 0,
    31  		))
    32  	}
    33  	return &CancellerLoop{
    34  		taskTimer: taskTimer,
    35  		getNow:    getNow,
    36  		interval:  interval,
    37  	}
    38  }
    39  
    40  // Start starts a loop that requests TaskTimer to remove cancelled tasks at at given interval.
    41  // Cancelling of Start is controlled by ctx.
    42  //
    43  // If ctx is nil, Start immediately returns ErrInvalidArg.
    44  // If loop is already running in some goroutine, Start immediately returns ErrAlreadyStarted.
    45  func (l *CancellerLoop) Start(ctx context.Context) error {
    46  	if ctx == nil {
    47  		return ErrInvalidArg
    48  	}
    49  
    50  	if !l.setWorking() {
    51  		return ErrAlreadyStarted
    52  	}
    53  	defer l.setWorking(false)
    54  
    55  	ticker := time.NewTicker(l.interval)
    56  	defer ticker.Stop()
    57  
    58  loop:
    59  	for {
    60  		select {
    61  		case <-ctx.Done():
    62  			break loop
    63  		case <-ticker.C:
    64  			removeCancelled(l.taskTimer, l.getNow)
    65  		}
    66  	}
    67  	return nil
    68  }
    69  
    70  func removeCancelled(taskTimer *TaskTimer, getNow common.GetNower) (removed bool) {
    71  	p := taskTimer.Peek()
    72  	if p != nil && p.scheduledTime.Sub(getNow.GetNow()) > time.Second {
    73  		// Racy Push may add min element in between previous Peek and this RemoveCancelled.
    74  		// But it is ok because each taskTimer method is thread safe.
    75  		// Benchmark shows RemoveCancelled takes only a few micro secs.
    76  		taskTimer.RemoveCancelled(0, 10_000)
    77  		return true
    78  	}
    79  	return false
    80  }