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 }