github.com/alwitt/goutils@v0.6.4/timer.go (about)

     1  package goutils
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/apex/log"
    10  )
    11  
    12  // TimeoutHandler callback function signature called timer timeout
    13  type TimeoutHandler func() error
    14  
    15  // IntervalTimer is a support interface for triggering events at specific intervals
    16  type IntervalTimer interface {
    17  	/*
    18  		Start starts timer with a specific timeout interval, and the callback to trigger on timeout.
    19  
    20  		If oneShort, cancel after first timeout.
    21  
    22  		 @param interval time.Duration - timeout interval
    23  		 @param handler TimeoutHandler - handler to trigger on timeout
    24  		 @param oneShort bool - if true, timer stop after first activation
    25  	*/
    26  	Start(interval time.Duration, handler TimeoutHandler, oneShort bool) error
    27  
    28  	/*
    29  		Stop stops the timer
    30  	*/
    31  	Stop() error
    32  }
    33  
    34  // intervalTimerImpl implements IntervalTimer
    35  type intervalTimerImpl struct {
    36  	Component
    37  	running          bool
    38  	rootContext      context.Context
    39  	operationContext context.Context
    40  	contextCancel    context.CancelFunc
    41  	wg               *sync.WaitGroup
    42  }
    43  
    44  /*
    45  GetIntervalTimerInstance get an implementation instance of IntervalTimer
    46  
    47  	@param rootCtxt context.Context - the base Context the timer will derive new runtime context from
    48  	  each time Start is called.
    49  	@param wg *sync.WaitGroup - WaitGroup use by timer
    50  	@param logTags log.Fields - log metadata fields
    51  	@return an IntervalTimer instance
    52  */
    53  func GetIntervalTimerInstance(
    54  	rootCtxt context.Context, wg *sync.WaitGroup, logTags log.Fields,
    55  ) (IntervalTimer, error) {
    56  	return &intervalTimerImpl{
    57  		Component:        Component{LogTags: logTags},
    58  		running:          false,
    59  		rootContext:      rootCtxt,
    60  		operationContext: nil,
    61  		contextCancel:    nil,
    62  		wg:               wg,
    63  	}, nil
    64  }
    65  
    66  /*
    67  Start starts timer with a specific timeout interval, and the callback to trigger on timeout.
    68  
    69  If oneShort, cancel after first timeout.
    70  
    71  	@param interval time.Duration - timeout interval
    72  	@param handler TimeoutHandler - handler to trigger on timeout
    73  	@param oneShort bool - if true, timer stop after first activation
    74  */
    75  func (t *intervalTimerImpl) Start(
    76  	interval time.Duration, handler TimeoutHandler, oneShot bool,
    77  ) error {
    78  	if t.running {
    79  		return fmt.Errorf("already running")
    80  	}
    81  	log.WithFields(t.LogTags).Infof("Starting with int %s", interval)
    82  	t.wg.Add(1)
    83  	t.running = true
    84  	ctxt, cancel := context.WithCancel(t.rootContext)
    85  	t.operationContext = ctxt
    86  	t.contextCancel = cancel
    87  	go func() {
    88  		defer t.wg.Done()
    89  		defer log.WithFields(t.LogTags).Info("Timer loop exiting")
    90  		defer func() {
    91  			t.running = false
    92  		}()
    93  		finished := false
    94  		for !finished {
    95  			select {
    96  			case <-t.operationContext.Done():
    97  				finished = true
    98  			case <-time.After(interval):
    99  				log.WithFields(t.LogTags).Debug("Calling handler")
   100  				if err := handler(); err != nil {
   101  					log.WithError(err).WithFields(t.LogTags).Error("Handler failed")
   102  				}
   103  				if oneShot {
   104  					return
   105  				}
   106  			}
   107  		}
   108  	}()
   109  	return nil
   110  }
   111  
   112  /*
   113  Stop stops the timer
   114  */
   115  func (t *intervalTimerImpl) Stop() error {
   116  	if t.contextCancel != nil {
   117  		log.WithFields(t.LogTags).Info("Stopping timer loop")
   118  		t.contextCancel()
   119  	}
   120  	return nil
   121  }
   122  
   123  // ========================================================================================
   124  
   125  // Sequencer is a helper interface for returning a sequence of numbers
   126  type Sequencer interface {
   127  	/*
   128  		NextValue returns the next value in the sequence
   129  	*/
   130  	NextValue() float64
   131  }
   132  
   133  // exponentialSequence is a helper interface to get an exponential sequence from a
   134  // starting value
   135  type exponentialSequence struct {
   136  	current    float64
   137  	growthRate float64
   138  }
   139  
   140  /*
   141  NextValue returns the next value in the sequence
   142  */
   143  func (s *exponentialSequence) NextValue() float64 {
   144  	nextValue := s.current * s.growthRate
   145  	s.current = nextValue
   146  	return nextValue
   147  }
   148  
   149  /*
   150  GetExponentialSeq define an exponential sequencer
   151  
   152  	@param initial float64 - initial value
   153  	@param growthRate float64 - EXP change rate
   154  	@return an Sequencer instance
   155  */
   156  func GetExponentialSeq(initial float64, growthRate float64) (Sequencer, error) {
   157  	if growthRate < 1.0 {
   158  		return nil, fmt.Errorf("growth rate of exponential sequence must be > 1.0")
   159  	}
   160  	return &exponentialSequence{current: initial, growthRate: growthRate}, nil
   161  }