github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/trigger.go (about)

     1  // Copyright 2018-2020 (c) Cognizant Digital Business, Evolutionary AI. All rights reserved. Issued under the Apache 2.0 License.
     2  
     3  package runner
     4  
     5  // This file implements a timer and a trigger channel together into a single
     6  // channel, this is useful for when we wish an action to be triggered on
     7  // a regular basis and also want to allow a caller to manually invoke, or
     8  // trigger and action.  This is often used in testing.
     9  
    10  import (
    11  	"time"
    12  
    13  	"github.com/lthibault/jitterbug"
    14  )
    15  
    16  // Trigger is a data structure that encapsulates a timer and a channel which together are used to in turn
    17  // to send messages to a downstream go channel.  The main Trigger use case is to allow a regular action
    18  // to be scheduled via a timer and also to allow unit tests for example to activate the same action.
    19  //
    20  type Trigger struct {
    21  	quitC chan struct{}
    22  	tick  *jitterbug.Ticker
    23  	T     <-chan struct{}
    24  	C     chan time.Time
    25  }
    26  
    27  // NewTrigger accepts a timer and a channel that together can be used to send messages
    28  // into a channel that is encapsulated within the returned t data structure
    29  //
    30  func NewTrigger(triggerC <-chan struct{}, d time.Duration, j jitterbug.Jitter) (t *Trigger) {
    31  	t = &Trigger{
    32  		tick:  jitterbug.New(d, j),
    33  		T:     triggerC,
    34  		C:     make(chan time.Time, 1),
    35  		quitC: make(chan struct{}, 1),
    36  	}
    37  	go t.loop()
    38  	return t
    39  }
    40  
    41  // Stop will close the internal channel used to signal termination to the
    42  // internally running go routine that processes the timer and the trigger
    43  // chanel
    44  //
    45  func (t *Trigger) Stop() {
    46  	close(t.quitC)
    47  }
    48  
    49  // loop is the internal service go routine that will accept either a timer,
    50  // or the manually notified channel to trigger the downstream channel.
    51  //
    52  // loop also listens for termnination and will tear everything down if
    53  // that occurs
    54  //
    55  func (t *Trigger) loop() {
    56  	defer func() {
    57  		t.tick.Stop()
    58  
    59  		close(t.C)
    60  
    61  		// Typically the termination will be seen as a nil
    62  		// message on the channel which is the close occurring
    63  		// elsewhere.  Close again for safety sake but
    64  		// ignore a panic if the channel is already down
    65  		defer func() {
    66  			_ = recover()
    67  		}()
    68  
    69  		close(t.quitC)
    70  	}()
    71  
    72  	for {
    73  		select {
    74  		case <-t.quitC:
    75  			return
    76  		case <-t.tick.C:
    77  			t.signal()
    78  		case <-t.T:
    79  			t.signal()
    80  		}
    81  	}
    82  }
    83  
    84  // signal is called to trigger the downstream channel with a timeout if
    85  // no one is listening in order that it does not block
    86  //
    87  func (t *Trigger) signal() {
    88  	select {
    89  	case t.C <- time.Now():
    90  	case <-time.After(200 * time.Millisecond):
    91  	}
    92  }