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  }