github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/internal/notifications/notifications.go (about)

     1  // Copyright 2018 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package internal contains miscellaneous small components used internally by
    16  // the Fleetspeak server.
    17  package notifications
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  
    23  	log "github.com/golang/glog"
    24  	"github.com/google/fleetspeak/fleetspeak/src/common"
    25  	"golang.org/x/time/rate"
    26  )
    27  
    28  // Limit to 50 bulk notification calls per second.
    29  const bulkNotificationMaxRate = rate.Limit(50.0)
    30  
    31  // NoopListener implements notifications.Listener in a trivial way. It can be used
    32  // as a listener when no listener is actually needed. i.e., when streaming connections
    33  // are not being used.
    34  type NoopListener struct {
    35  	c chan common.ClientID
    36  }
    37  
    38  func (l *NoopListener) Start() (<-chan common.ClientID, error) {
    39  	l.c = make(chan common.ClientID)
    40  	return l.c, nil
    41  }
    42  func (l *NoopListener) Stop() {
    43  	close(l.c)
    44  }
    45  func (l *NoopListener) Address() string {
    46  	return ""
    47  }
    48  
    49  // NoopNotifier implements notifications.Listener in a trivial way. It can be
    50  // used as a Notifier when no Notifier is actually needed. i.e., when streaming
    51  // connections are not being used.
    52  type NoopNotifier struct{}
    53  
    54  func (n NoopNotifier) NewMessageForClient(ctx context.Context, target string, id common.ClientID) error {
    55  	return nil
    56  }
    57  
    58  // LocalListenerNotifier is both a Listener and a Notifier. It self notifies to
    59  // support streaming connections in a single server installation.
    60  type LocalListenerNotifier struct {
    61  	c chan common.ClientID
    62  	l sync.RWMutex
    63  }
    64  
    65  func (n *LocalListenerNotifier) Start() (<-chan common.ClientID, error) {
    66  	n.c = make(chan common.ClientID)
    67  	return n.c, nil
    68  }
    69  
    70  func (n *LocalListenerNotifier) Stop() {
    71  	n.l.Lock()
    72  	close(n.c)
    73  	n.c = nil
    74  	n.l.Unlock()
    75  }
    76  
    77  func (n *LocalListenerNotifier) Address() string {
    78  	return "local"
    79  }
    80  
    81  func (n *LocalListenerNotifier) NewMessageForClient(ctx context.Context, target string, id common.ClientID) error {
    82  	if target != "local" {
    83  		log.Warningf("Attempt to send non-local notification. Igoring.")
    84  		return nil
    85  	}
    86  	n.l.RLock()
    87  	defer n.l.RUnlock()
    88  
    89  	select {
    90  	case <-ctx.Done():
    91  		return ctx.Err()
    92  	case n.c <- id:
    93  	}
    94  	return nil
    95  }
    96  
    97  // A Dispatcher connects dispatches incoming notifications according to the
    98  // client that they are for.
    99  type Dispatcher struct {
   100  	l   sync.RWMutex
   101  	m   map[common.ClientID]chan<- struct{}
   102  	lim *rate.Limiter
   103  }
   104  
   105  func NewDispatcher() *Dispatcher {
   106  	return &Dispatcher{
   107  		m:   make(map[common.ClientID]chan<- struct{}),
   108  		lim: rate.NewLimiter(bulkNotificationMaxRate, 20),
   109  	}
   110  }
   111  
   112  // Dispatch sends a notification to the most recent registration for id. It is a
   113  // no-op if there is already a notification pending for the id.
   114  func (d *Dispatcher) Dispatch(id common.ClientID) {
   115  	d.l.RLock()
   116  	defer d.l.RUnlock()
   117  
   118  	c, ok := d.m[id]
   119  	if ok {
   120  		select {
   121  		case c <- struct{}{}:
   122  		default:
   123  			// channel is already pending - no need to add another signal to it.
   124  		}
   125  	}
   126  }
   127  
   128  // Register creates a registration for id. Once called, any call to Dispatch for
   129  // id will cause a notification to passed through notice.
   130  //
   131  // The registration will be cleared and noticed will be closed when fin is
   132  // called, or if another registration for id is created.
   133  func (d *Dispatcher) Register(id common.ClientID) (notice <-chan struct{}, fin func()) {
   134  	// Buffered with length 1 - combined with non-blocking write, expected
   135  	// behavior that a notification can be buffered until the connection is ready
   136  	// to read it, with no real blocking possible of the Dispatch method.
   137  	ch := make(chan struct{}, 1)
   138  
   139  	d.l.Lock()
   140  	c, ok := d.m[id]
   141  	if ok {
   142  		close(c)
   143  	}
   144  	d.m[id] = ch
   145  	d.l.Unlock()
   146  
   147  	return ch, func() {
   148  		d.l.Lock()
   149  		defer d.l.Unlock()
   150  
   151  		c, ok := d.m[id]
   152  		if ok && c == ch {
   153  			close(c)
   154  			delete(d.m, id)
   155  		}
   156  	}
   157  }
   158  
   159  // NotifyAll effectively dispatches to every client currently registered.
   160  func (d *Dispatcher) NotifyAll(ctx context.Context) {
   161  	d.l.RLock()
   162  	ids := make([]common.ClientID, 0, len(d.m))
   163  	for k := range d.m {
   164  		ids = append(ids, k)
   165  	}
   166  	d.l.RUnlock()
   167  
   168  	for _, id := range ids {
   169  		if err := d.lim.Wait(ctx); err != nil {
   170  			// We are probably just out of time, trust any remaining clients to notice
   171  			// eventually, e.g. on reconnect or similar.
   172  			return
   173  		}
   174  		d.Dispatch(id)
   175  	}
   176  }