github.com/kubeshop/testkube@v1.17.23/pkg/tcl/testworkflowstcl/testworkflowcontroller/watcher.go (about) 1 // Copyright 2024 Testkube. 2 // 3 // Licensed as a Testkube Pro file under the Testkube Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt 8 9 package testworkflowcontroller 10 11 import ( 12 "context" 13 "slices" 14 "sync" 15 ) 16 17 type WatcherValue[T interface{}] struct { 18 Value T 19 Error error 20 } 21 22 type Watcher[T interface{}] interface { 23 Next(ctx context.Context) <-chan WatcherValue[T] 24 Any(ctx context.Context) <-chan WatcherValue[T] 25 Done() <-chan struct{} 26 Listen(fn func(WatcherValue[T], bool)) func() 27 Stream(ctx context.Context) WatcherChannel[T] 28 Close() 29 } 30 31 type watcher[T interface{}] struct { 32 ctx context.Context 33 ctxCancel context.CancelFunc 34 mu sync.Mutex 35 hasCh chan struct{} 36 ch chan WatcherValue[T] 37 listeners []*func(WatcherValue[T], bool) 38 paused bool 39 closed bool 40 41 cacheSize int 42 cacheOffset int 43 cache []WatcherValue[T] 44 45 readerCh chan<- struct{} 46 } 47 48 func newWatcher[T interface{}](ctx context.Context, cacheSize int) *watcher[T] { 49 finalCtx, ctxCancel := context.WithCancel(ctx) 50 return &watcher[T]{ 51 ctx: finalCtx, 52 ctxCancel: ctxCancel, 53 hasCh: make(chan struct{}), 54 ch: make(chan WatcherValue[T]), 55 cacheSize: cacheSize, 56 } 57 } 58 59 func (w *watcher[T]) Pause() { 60 w.mu.Lock() 61 defer w.mu.Unlock() 62 w.paused = true 63 if w.readerCh != nil { 64 close(w.readerCh) 65 w.readerCh = nil 66 } 67 } 68 69 func (w *watcher[T]) Resume() { 70 w.mu.Lock() 71 defer w.mu.Unlock() 72 w.paused = false 73 w.recomputeReader() 74 } 75 76 func (w *watcher[T]) Next(ctx context.Context) <-chan WatcherValue[T] { 77 ch := make(chan WatcherValue[T]) 78 var cancelListener func() 79 finalCtx, cancel := context.WithCancel(ctx) 80 var wg sync.WaitGroup 81 wg.Add(1) 82 go func() { 83 wg.Wait() 84 <-finalCtx.Done() 85 cancelListener() 86 }() 87 cancelListener = w.Listen(func(w WatcherValue[T], ok bool) { 88 wg.Wait() // on finished channel, the listener may be called before the lock goes down 89 cancelListener() 90 cancel() 91 if ok { 92 ch <- w 93 } 94 close(ch) 95 }) 96 wg.Done() 97 return ch 98 } 99 100 func (w *watcher[T]) Any(ctx context.Context) <-chan WatcherValue[T] { 101 ch := make(chan WatcherValue[T]) 102 go func() { 103 w.mu.Lock() 104 if len(w.cache) > 0 { 105 v := w.cache[len(w.cache)-1] 106 w.mu.Unlock() 107 ch <- v 108 close(ch) 109 return 110 } 111 w.mu.Unlock() 112 v, ok := <-w.Next(ctx) 113 if ok { 114 ch <- v 115 } 116 close(ch) 117 }() 118 return ch 119 } 120 121 func (w *watcher[T]) _send(v WatcherValue[T]) { 122 w.mu.Lock() 123 124 // Handle closed stream 125 if w.closed { 126 w.mu.Unlock() 127 return 128 } 129 130 // Save in cache 131 if w.cacheSize == 0 { 132 // Ignore cache 133 } else if w.cacheSize < 0 || w.cacheSize > len(w.cache) { 134 // Unlimited cache or still cache size 135 w.cache = append(w.cache, v) 136 } else { 137 // Emptying oldest entries in the cache 138 for i := 1; i < len(w.cache); i++ { 139 w.cache[i-1] = w.cache[i] 140 } 141 w.cache[len(w.cache)-1] = v 142 w.cacheOffset++ 143 } 144 w.mu.Unlock() 145 146 // Ignore the panic due to the channel closed externally 147 defer func() { 148 recover() 149 }() 150 151 // Emit the data to the live stream 152 w.hasCh <- struct{}{} 153 w.ch <- v 154 } 155 156 func (w *watcher[T]) SendValue(value T) { 157 w._send(WatcherValue[T]{Value: value}) 158 159 } 160 161 func (w *watcher[T]) SendError(err error) { 162 w._send(WatcherValue[T]{Error: err}) 163 } 164 165 func (w *watcher[T]) Close() { 166 w.mu.Lock() 167 if !w.closed { 168 w.ctxCancel() 169 ch := w.ch 170 w.closed = true 171 close(ch) 172 close(w.hasCh) 173 w.mu.Unlock() 174 } else { 175 w.mu.Unlock() 176 } 177 } 178 179 func (w *watcher[T]) recomputeReader() { 180 if w.paused { 181 return 182 } 183 shouldRead := !w.closed && len(w.listeners) > 0 184 if shouldRead && w.readerCh == nil { 185 // Start the reader 186 ch := make(chan struct{}) 187 w.readerCh = ch 188 go func() { 189 // Prioritize cancel channels 190 for { 191 select { 192 case <-ch: 193 return 194 default: 195 } 196 // Then wait for the results 197 select { 198 case <-ch: 199 return 200 case _, ok := <-w.hasCh: 201 listeners := slices.Clone(w.listeners) 202 if ok { 203 select { 204 case <-ch: 205 go func() { 206 defer func() { 207 recover() 208 }() 209 w.hasCh <- struct{}{} // replay hasCh in case it is needed in next iteration 210 }() 211 return 212 default: 213 } 214 } 215 value, ok := <-w.ch 216 var wg sync.WaitGroup 217 for _, l := range listeners { 218 wg.Add(1) 219 go func(fn func(WatcherValue[T], bool)) { 220 defer func() { 221 recover() 222 wg.Done() 223 }() 224 fn(value, ok) 225 }(*l) 226 } 227 wg.Wait() 228 } 229 } 230 }() 231 } else if !shouldRead && w.readerCh != nil { 232 // Stop the reader 233 close(w.readerCh) 234 w.readerCh = nil 235 } 236 } 237 238 func (w *watcher[T]) stop(ptr *func(WatcherValue[T], bool)) { 239 w.mu.Lock() 240 defer w.mu.Unlock() 241 index := slices.Index(w.listeners, ptr) 242 if index == -1 { 243 return 244 } 245 // Delete the listener and stop a base channel reader if needed 246 *w.listeners[index] = func(value WatcherValue[T], ok bool) {} 247 w.listeners = append(w.listeners[0:index], w.listeners[index+1:]...) 248 w.recomputeReader() 249 } 250 251 func (w *watcher[T]) listenUnsafe(fn func(WatcherValue[T], bool)) func() { 252 // Fail immediately if the watcher is already closed 253 if w.closed { 254 go func() { 255 fn(WatcherValue[T]{}, false) 256 }() 257 return func() {} 258 } 259 260 // Append new listener and start a base channel reader if needed 261 ptr := &fn 262 w.listeners = append(w.listeners, ptr) 263 w.recomputeReader() 264 return func() { 265 w.stop(ptr) 266 } 267 } 268 269 func (w *watcher[T]) Listen(fn func(WatcherValue[T], bool)) func() { 270 w.mu.Lock() 271 defer w.mu.Unlock() 272 return w.listenUnsafe(fn) 273 } 274 275 func (w *watcher[T]) Done() <-chan struct{} { 276 return w.ctx.Done() 277 } 278 279 func (w *watcher[T]) getAndLock(index int) (WatcherValue[T], int, bool) { 280 w.mu.Lock() 281 index -= w.cacheOffset 282 if index < 0 { 283 index = 0 284 } 285 next := index + w.cacheOffset + 1 286 287 // Load value from cache 288 if index < len(w.cache) { 289 return w.cache[index], next, true 290 } 291 292 // Fetch next result 293 return WatcherValue[T]{}, next, false 294 } 295 296 func (w *watcher[T]) Stream(ctx context.Context) WatcherChannel[T] { 297 // Create the channel 298 wCh := &watcherChannel[T]{ 299 ch: make(chan WatcherValue[T]), 300 } 301 302 // Handle context 303 finalCtx, cancel := context.WithCancel(ctx) 304 go func() { 305 <-finalCtx.Done() 306 wCh.Stop() 307 }() 308 309 // Fast-track when there are no cached messages 310 w.mu.Lock() 311 if len(w.cache) == 0 { 312 wCh.cancel = w.listenUnsafe(func(v WatcherValue[T], ok bool) { 313 defer func() { 314 // Ignore writing to already closed channel 315 recover() 316 }() 317 if ok { 318 wCh.ch <- v 319 } else if wCh.ch != nil { 320 wCh.Stop() 321 cancel() 322 } 323 }) 324 w.mu.Unlock() 325 return wCh 326 } 327 w.mu.Unlock() 328 329 // Pick cache data 330 go func() { 331 defer func() { 332 // Ignore writing to already closed channel 333 recover() 334 }() 335 336 if wCh.ch == nil { 337 cancel() 338 return 339 } 340 341 // Send cache data 342 wCh.cancel = func() { cancel() } 343 var value WatcherValue[T] 344 var ok bool 345 index := 0 346 for value, index, ok = w.getAndLock(index); ok; value, index, ok = w.getAndLock(index) { 347 if wCh.ch == nil { 348 w.mu.Unlock() 349 cancel() 350 return 351 } 352 w.mu.Unlock() 353 wCh.ch <- value 354 } 355 356 if wCh.ch == nil { 357 w.mu.Unlock() 358 cancel() 359 return 360 } 361 362 // Start actually listening 363 wCh.cancel = w.listenUnsafe(func(v WatcherValue[T], ok bool) { 364 defer func() { 365 // Ignore writing to already closed channel 366 recover() 367 }() 368 if ok { 369 wCh.ch <- v 370 } else if wCh.ch != nil { 371 wCh.Stop() 372 cancel() 373 } 374 }) 375 w.mu.Unlock() 376 }() 377 378 return wCh 379 } 380 381 type WatcherChannel[T interface{}] interface { 382 Channel() <-chan WatcherValue[T] 383 Stop() 384 } 385 386 type watcherChannel[T interface{}] struct { 387 cancel func() 388 ch chan WatcherValue[T] 389 } 390 391 func (w *watcherChannel[T]) Channel() <-chan WatcherValue[T] { 392 if w.ch == nil { 393 ch := make(chan WatcherValue[T]) 394 close(ch) 395 return ch 396 } 397 return w.ch 398 } 399 400 func (w *watcherChannel[T]) Stop() { 401 if w.cancel != nil { 402 w.cancel() 403 w.cancel = nil 404 if w.ch != nil { 405 ch := w.ch 406 w.ch = nil 407 close(ch) 408 } 409 } 410 }