github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/timerwatcher/timer.go (about) 1 // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package timerwatcher 6 7 import ( 8 "context" 9 "fmt" 10 "math/rand" 11 "sync" 12 "time" 13 14 "github.com/choria-io/go-choria/aagent/model" 15 "github.com/choria-io/go-choria/aagent/util" 16 "github.com/choria-io/go-choria/aagent/watchers/event" 17 "github.com/choria-io/go-choria/aagent/watchers/watcher" 18 ) 19 20 type State int 21 22 const ( 23 Stopped State = iota 24 Running 25 26 wtype = "timer" 27 version = "v1" 28 ) 29 30 var stateNames = map[State]string{ 31 Running: "running", 32 Stopped: "stopped", 33 } 34 35 type properties struct { 36 Timer time.Duration 37 Splay bool 38 } 39 40 type Watcher struct { 41 *watcher.Watcher 42 properties *properties 43 44 name string 45 machine model.Machine 46 state State 47 48 terminate chan struct{} 49 cancelTimer func() 50 mu *sync.Mutex 51 } 52 53 func New(machine model.Machine, name string, states []string, failEvent string, successEvent string, interval string, ai time.Duration, properties map[string]any) (any, error) { 54 var err error 55 56 if successEvent != "" { 57 return nil, fmt.Errorf("timer watcher does not support success events") 58 } 59 60 tw := &Watcher{ 61 name: name, 62 machine: machine, 63 state: 0, 64 terminate: make(chan struct{}), 65 mu: &sync.Mutex{}, 66 } 67 68 tw.Watcher, err = watcher.NewWatcher(name, wtype, ai, states, machine, failEvent, successEvent) 69 if err != nil { 70 return nil, err 71 } 72 73 err = tw.setProperties(properties) 74 if err != nil { 75 return nil, fmt.Errorf("could not set properties: %s", err) 76 } 77 78 return tw, nil 79 } 80 81 func (w *Watcher) Delete() { 82 close(w.terminate) 83 } 84 85 func (w *Watcher) forceTimerStop() { 86 w.mu.Lock() 87 cancel := w.cancelTimer 88 w.mu.Unlock() 89 90 if cancel != nil { 91 w.Infof("Stopping timer early on state transition to %s", w.machine.State()) 92 cancel() 93 } 94 } 95 96 func (w *Watcher) timeStart() { 97 w.mu.Lock() 98 cancel := w.cancelTimer 99 w.mu.Unlock() 100 101 if cancel != nil { 102 w.Infof("Timer was running, resetting to %v", w.properties.Timer) 103 cancel() 104 } 105 106 go func() { 107 timer := time.NewTimer(w.properties.Timer) 108 ctx, cancel := context.WithCancel(context.Background()) 109 110 w.mu.Lock() 111 w.state = Running 112 w.cancelTimer = cancel 113 w.mu.Unlock() 114 115 w.NotifyWatcherState(w.CurrentState()) 116 117 select { 118 case <-timer.C: 119 w.mu.Lock() 120 w.state = Stopped 121 if w.cancelTimer != nil { 122 w.cancelTimer() 123 } 124 w.cancelTimer = nil 125 w.mu.Unlock() 126 127 w.NotifyWatcherState(w.CurrentState()) 128 w.FailureTransition() 129 130 case <-ctx.Done(): 131 w.mu.Lock() 132 w.cancelTimer = nil 133 timer.Stop() 134 w.state = Stopped 135 w.mu.Unlock() 136 137 w.NotifyWatcherState(w.CurrentState()) 138 139 case <-w.terminate: 140 w.mu.Lock() 141 w.cancelTimer = nil 142 w.state = Stopped 143 w.mu.Unlock() 144 return 145 } 146 }() 147 } 148 149 func (w *Watcher) watch() { 150 if !w.ShouldWatch() { 151 w.forceTimerStop() 152 return 153 } 154 155 w.Infof("Starting timer") 156 w.timeStart() 157 } 158 159 func (w *Watcher) Run(ctx context.Context, wg *sync.WaitGroup) { 160 defer wg.Done() 161 162 w.Infof("Timer watcher starting with %v timer", w.properties.Timer) 163 164 // handle initial state 165 w.watch() 166 167 for { 168 select { 169 case <-w.StateChangeC(): 170 w.watch() 171 case <-w.terminate: 172 w.Infof("Handling terminate notification") 173 return 174 case <-ctx.Done(): 175 w.Infof("Stopping on context interrupt") 176 return 177 } 178 } 179 } 180 181 func (w *Watcher) validate() error { 182 if w.properties.Timer < time.Second { 183 w.properties.Timer = time.Second 184 } 185 186 if w.properties.Splay { 187 w.properties.Timer = time.Duration(rand.Int63n(int64(w.properties.Timer))) 188 w.Infof("Adjusting timer to %v due to splay setting", w.properties.Timer) 189 } 190 191 return nil 192 } 193 194 func (w *Watcher) setProperties(props map[string]any) error { 195 if w.properties == nil { 196 w.properties = &properties{} 197 } 198 199 err := util.ParseMapStructure(props, w.properties) 200 if err != nil { 201 return err 202 } 203 204 return w.validate() 205 } 206 207 func (w *Watcher) CurrentState() any { 208 w.mu.Lock() 209 defer w.mu.Unlock() 210 211 s := &StateNotification{ 212 Event: event.New(w.name, wtype, version, w.machine), 213 State: stateNames[w.state], 214 Timer: w.properties.Timer, 215 } 216 217 return s 218 }