go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/clock/testclock/testtimer.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package testclock 16 17 import ( 18 "context" 19 "time" 20 21 "go.chromium.org/luci/common/clock" 22 ) 23 24 // timerClock encapsulates clock implementation for use by the `timer` type. 25 type timerClock interface { 26 Now() time.Time 27 addPendingTimer(t *timer, d time.Duration, triggerC chan<- time.Time) 28 clearPendingTimer(t *timer) 29 } 30 31 // timer is an implementation of clock.TestTimer that uses a channel 32 // to signal the timer to fire. 33 // 34 // The channel is buffered so it can be used without requiring a separate 35 // signalling goroutine. 36 type timer struct { 37 ctx context.Context 38 clock timerClock 39 40 // tags is the set of tags in the Context when this timer was created. 41 tags []string 42 43 // afterC will have the TimerResult of the timer's expiration written to it 44 // when this timer triggers or is canceled. 45 afterC chan clock.TimerResult 46 47 // monitorFinishedC is used by our timer monitor to signal that the timer has 48 // stopped. 49 monitorFinishedC chan bool 50 } 51 52 var _ clock.Timer = (*timer)(nil) 53 54 // NewTimer returns a new, instantiated timer. 55 func newTimer(ctx context.Context, clk timerClock) *timer { 56 return &timer{ 57 ctx: ctx, 58 clock: clk, 59 tags: clock.Tags(ctx), 60 afterC: make(chan clock.TimerResult, 1), 61 } 62 } 63 64 func (t *timer) GetC() <-chan clock.TimerResult { 65 return t.afterC 66 } 67 68 func (t *timer) Reset(d time.Duration) (active bool) { 69 if d < 0 { 70 d = 0 71 } 72 73 // Stop our current polling goroutine, if it's running. 74 active = t.Stop() 75 76 // Add ourself to our Clock's scheduler. 77 triggerC := make(chan time.Time, 1) 78 t.clock.addPendingTimer(t, d, triggerC) 79 80 // Start a timer monitor goroutine. 81 t.monitorFinishedC = make(chan bool, 1) 82 go t.monitor(triggerC, t.monitorFinishedC) 83 return 84 } 85 86 func (t *timer) Stop() (stopped bool) { 87 // If the timer is not running, we're done. 88 if t.monitorFinishedC == nil { 89 return false 90 } 91 92 // Shutdown and wait for our monitoring goroutine. 93 t.clock.clearPendingTimer(t) 94 stopped = <-t.monitorFinishedC 95 96 // Clear state. 97 t.monitorFinishedC = nil 98 return 99 } 100 101 // GetTags returns the tags associated with the specified timer. If the timer 102 // has no tags, an empty slice (nil) will be returned. 103 func GetTags(t clock.Timer) []string { 104 if tt, ok := t.(*timer); ok { 105 return clock.Tags(tt.ctx) 106 } 107 return nil 108 } 109 110 // HasTags tests if a given timer has the same tags. 111 func HasTags(t clock.Timer, first string, tags ...string) bool { 112 timerTags := GetTags(t) 113 if len(timerTags) == 0 { 114 return false 115 } 116 117 if first != timerTags[0] { 118 return false 119 } 120 121 if len(timerTags) > 1 { 122 // Compare the remainder. 123 timerTags = timerTags[1:] 124 if len(timerTags) != len(tags) { 125 return false 126 } 127 128 for i, tag := range timerTags { 129 if tags[i] != tag { 130 return false 131 } 132 } 133 } 134 return true 135 } 136 137 // monitor is run in a goroutine, monitoring the timer Context for 138 // cancellation and forcing a premature timer completion if the monitor 139 // was canceled. 140 func (t *timer) monitor(triggerC chan time.Time, finishedC chan bool) { 141 monitorRunning := false 142 defer func() { 143 finishedC <- monitorRunning 144 close(finishedC) 145 }() 146 147 select { 148 case now, ok := <-triggerC: 149 if !ok { 150 // Our monitor goroutine has been reaped, probably because we were 151 // stopped. 152 monitorRunning = true 153 break 154 } 155 156 // The Clock has signalled that this timer has expired. 157 t.afterC <- clock.TimerResult{ 158 Time: now, 159 Err: nil, 160 } 161 162 case <-t.ctx.Done(): 163 // Our Context has been canceled. 164 t.clock.clearPendingTimer(t) 165 t.afterC <- clock.TimerResult{ 166 Time: t.clock.Now(), 167 Err: t.ctx.Err(), 168 } 169 } 170 }