github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/statebroadcast.go (about)

     1  // Copyright 2018-2020 (c) Cognizant Digital Business, Evolutionary AI. All rights reserved. Issued under the Apache 2.0 License.
     2  
     3  package runner
     4  
     5  import (
     6  	"context"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/rs/xid"
    11  
    12  	"github.com/jjeffery/kv" // MIT License
    13  )
    14  
    15  // This file contains the implementation of a channel fan-out
    16  // based on subscriptions.
    17  //
    18  
    19  // Listeners is used to handle the broadcasting of cluster events when Kubernetes is
    20  // being used
    21  type Listeners struct {
    22  	Master    chan K8sStateUpdate
    23  	listeners map[xid.ID]chan<- K8sStateUpdate
    24  	sync.Mutex
    25  }
    26  
    27  // NewStateBroadcast is used to instantiate a Kubernetes event broadcaster
    28  func NewStateBroadcast(ctx context.Context, errorC chan<- kv.Error) (l *Listeners) {
    29  	l = &Listeners{
    30  		Master:    make(chan K8sStateUpdate, 1),
    31  		listeners: map[xid.ID]chan<- K8sStateUpdate{},
    32  	}
    33  
    34  	go l.run(ctx, errorC)
    35  
    36  	return l
    37  }
    38  
    39  func (l *Listeners) run(ctx context.Context, errorC chan<- kv.Error) {
    40  	for {
    41  		select {
    42  		case <-ctx.Done():
    43  			return
    44  		case state := <-l.Master:
    45  
    46  			clients := make([]chan<- K8sStateUpdate, 0, len(l.listeners))
    47  
    48  			// Make a consistent copy of all the channels that the update will be sent down
    49  			// so that we retain the values at this moment in time
    50  			if len(l.listeners) != 0 {
    51  				l.Lock()
    52  				for _, v := range l.listeners {
    53  					clients = append(clients, v)
    54  				}
    55  				l.Unlock()
    56  			}
    57  
    58  			for _, c := range clients {
    59  				func() {
    60  					defer func() {
    61  						// There is a window of time in which the delete for a listener occurs
    62  						// between copying the collection of listeners and someone else
    63  						// deleting the listen and this function then doing a send
    64  						recover()
    65  					}()
    66  					select {
    67  					case c <- state:
    68  					case <-time.After(500 * time.Millisecond):
    69  					}
    70  				}()
    71  			}
    72  		}
    73  	}
    74  }
    75  
    76  // Add is used when a running thread wishes to add a channel to the broadcaster
    77  // on which Kubernetes events will be received
    78  func (l *Listeners) Add(listen chan<- K8sStateUpdate) (id xid.ID, err kv.Error) {
    79  
    80  	id = xid.New()
    81  
    82  	l.Lock()
    83  	l.listeners[id] = listen
    84  	l.Unlock()
    85  
    86  	return id, nil
    87  }
    88  
    89  // Delete is used when a running thread wishes to drop a channel from the broadcaster
    90  // on which Kubernetes events will be received
    91  func (l *Listeners) Delete(id xid.ID) {
    92  
    93  	l.Lock()
    94  	delete(l.listeners, id)
    95  	l.Unlock()
    96  }