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  }