github.com/cilium/cilium@v1.16.2/pkg/inctimer/inctimer.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package inctimer 5 6 import "time" 7 8 // IncTimer should be the preferred mechanism over 9 // calling `time.After` when wanting an `After`-like 10 // function in a loop. This prevents memory build up 11 // as the `time.After` method creates a new timer 12 // instance every time it is called, and it is not 13 // garbage collected until after it fires. Conversely, 14 // IncTimer only uses one timer and correctly stops 15 // the timer, clears its channel, and resets it 16 // everytime that `After` is called. 17 type IncTimer interface { 18 After(time.Duration) <-chan time.Time 19 } 20 21 type incTimer struct { 22 t *time.Timer 23 } 24 25 // New creates a new IncTimer and a done function. 26 // IncTimer only uses one timer and correctly stops 27 // the timer, clears the channel, and resets it every 28 // time the `After` function is called. 29 // WARNING: Concurrent use is not expected. The use 30 // of this timer should be for only one goroutine. 31 func New() (IncTimer, func() bool) { 32 it := &incTimer{} 33 return it, it.stop 34 } 35 36 // stop returns true if a scheduled timer has been stopped before execution. 37 func (it *incTimer) stop() bool { 38 if it.t == nil { 39 return false 40 } 41 return it.t.Stop() 42 } 43 44 // After returns a channel that will fire after 45 // the specified duration. 46 func (it *incTimer) After(d time.Duration) <-chan time.Time { 47 // Stop the previous timer (if any) to garbage collect it. 48 // The old timer channel will be garbage collected even if not drained. 49 it.stop() 50 51 // We have to create a new timer for each invocation, because it is not 52 // possible to safely use https://golang.org/pkg/time/#Timer.Reset if we 53 // do not know if the timer channel has already been drained or not (which 54 // is the case here, as the client might have drained the channel already). 55 // Even after stopping a timer, it's not safe to attempt to drain its 56 // timer channel with a default case (for the case where the client has 57 // drained the channel already), as there is a small window where a timer 58 // is considered expired, but the channel has not received a value yet [1]. 59 // This would cause us to erroneously take the default case (assuming the 60 // channel has been drained by the client), when in fact the channel just 61 // has not received a value yet. Because the two cases (client has drained 62 // vs. value not received yet) are indistinguishable for us, we cannot use 63 // Timer.Reset and need to create a new timer. 64 // 65 // [1] The reason why this small window occurs, is because the Go runtime 66 // will remove a timer from the heap and and mark it as deleted _before_ 67 // it actually executes the timer function f: 68 // https://github.com/golang/go/blob/go1.16/src/runtime/time.go#L876 69 // This causes t.Stop to report the timer as already expired while it is 70 // in fact currently running: 71 // https://github.com/golang/go/blob/go1.16/src/runtime/time.go#L352 72 it.t = time.NewTimer(d) 73 return it.t.C 74 } 75 76 // After wraps the time.After function to get around the /timeafter linter 77 // warning for cases where it is inconvenient to use the instantiated version. 78 func After(d time.Duration) <-chan time.Time { 79 return time.After(d) 80 }