github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/event/targetlist.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package event 19 20 import ( 21 "context" 22 "fmt" 23 "runtime" 24 "sync" 25 "sync/atomic" 26 27 "github.com/minio/minio/internal/logger" 28 "github.com/minio/minio/internal/store" 29 "github.com/minio/pkg/v2/workers" 30 ) 31 32 const ( 33 // The maximum allowed number of concurrent Send() calls to all configured notifications targets 34 maxConcurrentAsyncSend = 50000 35 ) 36 37 // Target - event target interface 38 type Target interface { 39 ID() TargetID 40 IsActive() (bool, error) 41 Save(Event) error 42 SendFromStore(store.Key) error 43 Close() error 44 Store() TargetStore 45 } 46 47 // TargetStore is a shallow version of a target.Store 48 type TargetStore interface { 49 Len() int 50 } 51 52 // Stats is a collection of stats for multiple targets. 53 type Stats struct { 54 TotalEvents int64 // Deprecated 55 EventsSkipped int64 56 CurrentQueuedCalls int64 // Deprecated 57 EventsErrorsTotal int64 // Deprecated 58 CurrentSendCalls int64 // Deprecated 59 60 TargetStats map[TargetID]TargetStat 61 } 62 63 // TargetStat is the stats of a single target. 64 type TargetStat struct { 65 CurrentSendCalls int64 // CurrentSendCalls is the number of concurrent async Send calls to all targets 66 CurrentQueue int // Populated if target has a store. 67 TotalEvents int64 68 FailedEvents int64 // Number of failed events per target 69 } 70 71 // TargetList - holds list of targets indexed by target ID. 72 type TargetList struct { 73 // The number of concurrent async Send calls to all targets 74 currentSendCalls atomic.Int64 75 totalEvents atomic.Int64 76 eventsSkipped atomic.Int64 77 eventsErrorsTotal atomic.Int64 78 79 sync.RWMutex 80 targets map[TargetID]Target 81 queue chan asyncEvent 82 ctx context.Context 83 84 statLock sync.RWMutex 85 targetStats map[TargetID]targetStat 86 } 87 88 type targetStat struct { 89 // The number of concurrent async Send calls per targets 90 currentSendCalls int64 91 // The number of total events per target 92 totalEvents int64 93 // The number of failed events per target 94 failedEvents int64 95 } 96 97 func (list *TargetList) getStatsByTargetID(id TargetID) (stat targetStat) { 98 list.statLock.RLock() 99 defer list.statLock.RUnlock() 100 101 return list.targetStats[id] 102 } 103 104 func (list *TargetList) incCurrentSendCalls(id TargetID) { 105 list.statLock.Lock() 106 defer list.statLock.Unlock() 107 108 stats, ok := list.targetStats[id] 109 if !ok { 110 stats = targetStat{} 111 } 112 113 stats.currentSendCalls++ 114 list.targetStats[id] = stats 115 return 116 } 117 118 func (list *TargetList) decCurrentSendCalls(id TargetID) { 119 list.statLock.Lock() 120 defer list.statLock.Unlock() 121 122 stats, ok := list.targetStats[id] 123 if !ok { 124 // should not happen 125 return 126 } 127 128 stats.currentSendCalls-- 129 list.targetStats[id] = stats 130 return 131 } 132 133 func (list *TargetList) incFailedEvents(id TargetID) { 134 list.statLock.Lock() 135 defer list.statLock.Unlock() 136 137 stats, ok := list.targetStats[id] 138 if !ok { 139 stats = targetStat{} 140 } 141 142 stats.failedEvents++ 143 list.targetStats[id] = stats 144 return 145 } 146 147 func (list *TargetList) incTotalEvents(id TargetID) { 148 list.statLock.Lock() 149 defer list.statLock.Unlock() 150 151 stats, ok := list.targetStats[id] 152 if !ok { 153 stats = targetStat{} 154 } 155 156 stats.totalEvents++ 157 list.targetStats[id] = stats 158 return 159 } 160 161 type asyncEvent struct { 162 ev Event 163 targetSet TargetIDSet 164 } 165 166 // Add - adds unique target to target list. 167 func (list *TargetList) Add(targets ...Target) error { 168 list.Lock() 169 defer list.Unlock() 170 171 for _, target := range targets { 172 if _, ok := list.targets[target.ID()]; ok { 173 return fmt.Errorf("target %v already exists", target.ID()) 174 } 175 list.targets[target.ID()] = target 176 } 177 178 return nil 179 } 180 181 // Exists - checks whether target by target ID exists or not. 182 func (list *TargetList) Exists(id TargetID) bool { 183 list.RLock() 184 defer list.RUnlock() 185 186 _, found := list.targets[id] 187 return found 188 } 189 190 // TargetIDResult returns result of Remove/Send operation, sets err if 191 // any for the associated TargetID 192 type TargetIDResult struct { 193 // ID where the remove or send were initiated. 194 ID TargetID 195 // Stores any error while removing a target or while sending an event. 196 Err error 197 } 198 199 // Remove - closes and removes targets by given target IDs. 200 func (list *TargetList) Remove(targetIDSet TargetIDSet) { 201 list.Lock() 202 defer list.Unlock() 203 204 for id := range targetIDSet { 205 target, ok := list.targets[id] 206 if ok { 207 target.Close() 208 delete(list.targets, id) 209 } 210 } 211 } 212 213 // Targets - list all targets 214 func (list *TargetList) Targets() []Target { 215 if list == nil { 216 return []Target{} 217 } 218 219 list.RLock() 220 defer list.RUnlock() 221 222 targets := []Target{} 223 for _, tgt := range list.targets { 224 targets = append(targets, tgt) 225 } 226 227 return targets 228 } 229 230 // List - returns available target IDs. 231 func (list *TargetList) List() []TargetID { 232 list.RLock() 233 defer list.RUnlock() 234 235 keys := []TargetID{} 236 for k := range list.targets { 237 keys = append(keys, k) 238 } 239 240 return keys 241 } 242 243 func (list *TargetList) get(id TargetID) (Target, bool) { 244 list.RLock() 245 defer list.RUnlock() 246 247 target, ok := list.targets[id] 248 return target, ok 249 } 250 251 // TargetMap - returns available targets. 252 func (list *TargetList) TargetMap() map[TargetID]Target { 253 list.RLock() 254 defer list.RUnlock() 255 256 ntargets := make(map[TargetID]Target, len(list.targets)) 257 for k, v := range list.targets { 258 ntargets[k] = v 259 } 260 return ntargets 261 } 262 263 // Send - sends events to targets identified by target IDs. 264 func (list *TargetList) Send(event Event, targetIDset TargetIDSet, sync bool) { 265 if sync { 266 list.sendSync(event, targetIDset) 267 } else { 268 list.sendAsync(event, targetIDset) 269 } 270 } 271 272 func (list *TargetList) sendSync(event Event, targetIDset TargetIDSet) { 273 var wg sync.WaitGroup 274 for id := range targetIDset { 275 target, ok := list.get(id) 276 if !ok { 277 continue 278 } 279 wg.Add(1) 280 go func(id TargetID, target Target) { 281 list.currentSendCalls.Add(1) 282 list.incCurrentSendCalls(id) 283 list.incTotalEvents(id) 284 defer list.decCurrentSendCalls(id) 285 defer list.currentSendCalls.Add(-1) 286 defer wg.Done() 287 288 if err := target.Save(event); err != nil { 289 list.eventsErrorsTotal.Add(1) 290 list.incFailedEvents(id) 291 reqInfo := &logger.ReqInfo{} 292 reqInfo.AppendTags("targetID", id.String()) 293 logger.LogOnceIf(logger.SetReqInfo(context.Background(), reqInfo), err, id.String()) 294 } 295 }(id, target) 296 } 297 wg.Wait() 298 list.totalEvents.Add(1) 299 } 300 301 func (list *TargetList) sendAsync(event Event, targetIDset TargetIDSet) { 302 select { 303 case list.queue <- asyncEvent{ 304 ev: event, 305 targetSet: targetIDset.Clone(), 306 }: 307 case <-list.ctx.Done(): 308 list.eventsSkipped.Add(int64(len(list.queue))) 309 return 310 default: 311 list.eventsSkipped.Add(1) 312 err := fmt.Errorf("concurrent target notifications exceeded %d, configured notification target is too slow to accept events for the incoming request rate", maxConcurrentAsyncSend) 313 for id := range targetIDset { 314 reqInfo := &logger.ReqInfo{} 315 reqInfo.AppendTags("targetID", id.String()) 316 logger.LogOnceIf(logger.SetReqInfo(context.Background(), reqInfo), err, id.String()) 317 } 318 return 319 } 320 } 321 322 // Stats returns stats for targets. 323 func (list *TargetList) Stats() Stats { 324 t := Stats{} 325 if list == nil { 326 return t 327 } 328 t.CurrentSendCalls = list.currentSendCalls.Load() 329 t.EventsSkipped = list.eventsSkipped.Load() 330 t.TotalEvents = list.totalEvents.Load() 331 t.CurrentQueuedCalls = int64(len(list.queue)) 332 t.EventsErrorsTotal = list.eventsErrorsTotal.Load() 333 334 list.RLock() 335 defer list.RUnlock() 336 t.TargetStats = make(map[TargetID]TargetStat, len(list.targets)) 337 for id, target := range list.targets { 338 var currentQueue int 339 if st := target.Store(); st != nil { 340 currentQueue = st.Len() 341 } 342 stats := list.getStatsByTargetID(id) 343 t.TargetStats[id] = TargetStat{ 344 CurrentSendCalls: stats.currentSendCalls, 345 CurrentQueue: currentQueue, 346 FailedEvents: stats.failedEvents, 347 TotalEvents: stats.totalEvents, 348 } 349 } 350 351 return t 352 } 353 354 func (list *TargetList) startSendWorkers(workerCount int) { 355 if workerCount == 0 { 356 workerCount = runtime.GOMAXPROCS(0) 357 } 358 wk, err := workers.New(workerCount) 359 if err != nil { 360 panic(err) 361 } 362 for i := 0; i < workerCount; i++ { 363 wk.Take() 364 go func() { 365 defer wk.Give() 366 367 for { 368 select { 369 case av := <-list.queue: 370 list.sendSync(av.ev, av.targetSet) 371 case <-list.ctx.Done(): 372 return 373 } 374 } 375 }() 376 } 377 wk.Wait() 378 } 379 380 var startOnce sync.Once 381 382 // Init initialize target send workers. 383 func (list *TargetList) Init(workers int) *TargetList { 384 startOnce.Do(func() { 385 go list.startSendWorkers(workers) 386 }) 387 return list 388 } 389 390 // NewTargetList - creates TargetList. 391 func NewTargetList(ctx context.Context) *TargetList { 392 list := &TargetList{ 393 targets: make(map[TargetID]Target), 394 queue: make(chan asyncEvent, maxConcurrentAsyncSend), 395 targetStats: make(map[TargetID]targetStat), 396 ctx: ctx, 397 } 398 return list 399 }