github.com/haraldrudell/parl@v0.4.176/ptime/closing-ticker.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package ptime
     7  
     8  import (
     9  	"sync"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/haraldrudell/parl/internal/cyclebreaker"
    14  	"github.com/haraldrudell/parl/perrors"
    15  )
    16  
    17  // ClosingTicker is like time.Ticker but the channel C closes on shutdown.
    18  // A closing channel is detectable by listening threads.
    19  // If the computer is busy swapping, ticks will be lost.
    20  // MaxDuration indicates the longest time observed between ticks.
    21  // MaxDuration is normally 1 s
    22  type ClosingTicker struct {
    23  	C                 <-chan time.Time
    24  	MaxDuration       time.Duration // atomic int64
    25  	perrors.ParlError               // panic in Shutdown or panic in tick thread
    26  	isShutdownRequest chan struct{}
    27  	isTickThreadExit  chan struct{}
    28  	shutdownOnce      sync.Once
    29  }
    30  
    31  // NewClosingTicker returns a Ticker whose channel C closes on shutdown.
    32  // A closing channel is detectable by a listening thread.
    33  func NewClosingTicker(d time.Duration) (t *ClosingTicker) {
    34  	ch := make(chan time.Time)
    35  	ticker := time.NewTicker(d)
    36  	t0 := ClosingTicker{
    37  		C:                 ch,
    38  		isShutdownRequest: make(chan struct{}),
    39  		isTickThreadExit:  make(chan struct{}),
    40  	}
    41  
    42  	// luanch our tick thread
    43  	go t0.tick(ch, ticker)
    44  
    45  	return &t0
    46  }
    47  
    48  // Shutdown causes the channel C to close and resources to be released
    49  func (t *ClosingTicker) Shutdown() {
    50  	t.shutdownOnce.Do(func() {
    51  		defer cyclebreaker.Recover2(func() cyclebreaker.DA { return cyclebreaker.A() }, nil, t.AddErrorProc)
    52  
    53  		close(t.isShutdownRequest)
    54  		<-t.isTickThreadExit
    55  	})
    56  }
    57  
    58  func (t *ClosingTicker) GetError() (maxDuration time.Duration, err error) {
    59  	maxDuration = time.Duration(atomic.LoadInt64((*int64)(&t.MaxDuration)))
    60  	err = t.ParlError.GetError()
    61  	return
    62  }
    63  
    64  func (t *ClosingTicker) tick(out chan time.Time, ticker *time.Ticker) {
    65  	defer close(t.isTickThreadExit)
    66  	defer cyclebreaker.Recover2(func() cyclebreaker.DA { return cyclebreaker.A() }, nil, t.AddErrorProc)
    67  	defer close(out)
    68  	defer ticker.Stop()
    69  
    70  	var last time.Time
    71  	var maxDuration time.Duration
    72  
    73  	C := ticker.C
    74  	isShutdown := t.isShutdownRequest
    75  	for {
    76  		select {
    77  		case timeValue := <-C:
    78  			out <- timeValue
    79  			if !last.IsZero() {
    80  				d := timeValue.Sub(last)
    81  				if d > maxDuration {
    82  					maxDuration = d
    83  					atomic.StoreInt64((*int64)(&t.MaxDuration), int64(maxDuration))
    84  				}
    85  			}
    86  			last = timeValue
    87  			continue
    88  		case <-isShutdown:
    89  		}
    90  		break
    91  	}
    92  }