...

Source file src/golang.conradwood.net/go-easyops/utils/periodictimer.go

Documentation: golang.conradwood.net/go-easyops/utils

     1  package utils
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  )
    10  
    11  var (
    12  	debug_pt = flag.Bool("ge_debug_periodictimer", false, "debug the periodic timer")
    13  )
    14  
    15  type PeriodicTimer struct {
    16  	secs                  []time.Duration // sorted as Largest first
    17  	started               time.Time
    18  	callback              func(*PeriodicTimer, time.Duration) error
    19  	thread_running        bool // true if thread is running
    20  	stop_request          bool // true if thread is meant to exit
    21  	wait_chan             chan bool
    22  	lastSuccessfulRunSecs time.Duration
    23  	wasRunAtStart         bool // true if it was run at least once
    24  	lock                  sync.Mutex
    25  	runLock               sync.Mutex
    26  }
    27  
    28  /*
    29  A "PeriodicTimer" executes a callback at certain intervals over a certain period of time.
    30  For example, a periodictimer, defined as NewPeriodicTimer([]uint32{20,15,5}) will run for 20 seconds and call the callback
    31  after 5 seconds, 10 seconds and 20 seconds.
    32  The callback will be retried every second if it returns an error until it returns no error
    33  callback will also be called each time "Start()" is called.
    34  
    35  Note: this code is 'experimental' (at best). it is neither optimised for efficiency nor intended to be used for high-performance requirements. it is intented to be used for small, simple periodic tasks. a typical secs array may contain 5 entries. anything over 10 is considered large and untested.
    36  */
    37  func NewPeriodicTimer(secs []time.Duration, cb func(pt *PeriodicTimer, secsLapsed time.Duration) error) *PeriodicTimer {
    38  	if len(secs) == 0 {
    39  		secs = []time.Duration{time.Duration(0)}
    40  	}
    41  	sort.Slice(secs, func(i, j int) bool {
    42  		return secs[i] > secs[j]
    43  	})
    44  	pt := &PeriodicTimer{callback: cb, wait_chan: make(chan bool), secs: secs}
    45  	return pt
    46  }
    47  func (pt *PeriodicTimer) Start() {
    48  	pt.lock.Lock()
    49  	pt.started = time.Now()
    50  	pt.lastSuccessfulRunSecs = 0
    51  	pt.stop_request = false
    52  	pt.wasRunAtStart = false
    53  	if !pt.thread_running {
    54  		go pt.timerLoop()
    55  		pt.thread_running = true
    56  	}
    57  	err := pt.run_callback(0)
    58  	if err == nil {
    59  		pt.wasRunAtStart = true
    60  	}
    61  	pt.lock.Unlock()
    62  
    63  }
    64  func (pt *PeriodicTimer) Stop() {
    65  	pt.lock.Lock()
    66  	pt.stop_request = true
    67  	pt.lock.Unlock()
    68  }
    69  
    70  // wait for timer to either stop or expire
    71  func (pt *PeriodicTimer) Wait() {
    72  	<-pt.wait_chan
    73  	pt.stop_request = true
    74  }
    75  
    76  func (pt *PeriodicTimer) timerLoop() {
    77  	pt.debugf("timerloop started")
    78  	for {
    79  		if pt.stop_request {
    80  			break
    81  		}
    82  		time.Sleep(time.Duration(1) * time.Second)
    83  		if pt.stop_request {
    84  			break
    85  		}
    86  		pt.checkTime()
    87  
    88  		if pt.is_running_for_as_long_as_need_be() {
    89  			break
    90  		}
    91  	}
    92  	//finish loop
    93  	pt.lock.Lock()
    94  	pt.wait_chan <- true
    95  	pt.stop_request = false
    96  	if !pt.thread_running {
    97  		pt.thread_running = false
    98  	}
    99  	pt.lock.Unlock()
   100  	pt.debugf("timerloop finished")
   101  }
   102  func (pt *PeriodicTimer) is_running_for_as_long_as_need_be() bool {
   103  	rs := time.Since(pt.started)
   104  	if rs.Seconds() >= float64(pt.secs[0]) {
   105  		return true
   106  	}
   107  	return false
   108  }
   109  
   110  // periodically called by timer
   111  func (pt *PeriodicTimer) checkTime() {
   112  	secs := time.Since(pt.started)
   113  	// work out which period we're in
   114  	period := time.Duration(0)
   115  	for _, r := range pt.secs {
   116  		if r > secs {
   117  			continue
   118  		}
   119  		period = r
   120  		break
   121  	}
   122  	pt.debugf("period=%0.1fs, lastSucc=%0.1fs, wasrun=%v", period.Seconds(), pt.lastSuccessfulRunSecs.Seconds(), pt.wasRunAtStart)
   123  	if period == pt.lastSuccessfulRunSecs && pt.wasRunAtStart {
   124  		return
   125  	}
   126  
   127  	err := pt.run_callback(period)
   128  	if err == nil {
   129  		pt.lastSuccessfulRunSecs = period
   130  		pt.wasRunAtStart = true
   131  	}
   132  }
   133  func (pt *PeriodicTimer) run_callback(period time.Duration) error {
   134  	pt.runLock.Lock()
   135  	defer pt.runLock.Unlock()
   136  	err := pt.callback(pt, period)
   137  	return err
   138  }
   139  func (pt *PeriodicTimer) Secs() []time.Duration {
   140  	return pt.secs
   141  }
   142  func (pt *PeriodicTimer) LastStarted() time.Time {
   143  	return pt.started
   144  }
   145  
   146  func (pt *PeriodicTimer) debugf(format string, args ...interface{}) {
   147  	if *debug_pt == false {
   148  		return
   149  	}
   150  	d := time.Since(pt.LastStarted()).Seconds()
   151  	prefix := fmt.Sprintf("[periodictimer %v, runsince=%0.1fs] ", pt.Secs(), d)
   152  	x := fmt.Sprintf(format, args...)
   153  	fmt.Println(prefix + x)
   154  }
   155  

View as plain text