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 }