github.com/ava-labs/avalanchego@v1.11.11/network/throttling/inbound_msg_buffer_throttler.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package throttling
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	"github.com/ava-labs/avalanchego/ids"
    14  	"github.com/ava-labs/avalanchego/utils/metric"
    15  	"github.com/ava-labs/avalanchego/utils/wrappers"
    16  )
    17  
    18  // See inbound_msg_throttler.go
    19  
    20  func newInboundMsgBufferThrottler(
    21  	registerer prometheus.Registerer,
    22  	maxProcessingMsgsPerNode uint64,
    23  ) (*inboundMsgBufferThrottler, error) {
    24  	t := &inboundMsgBufferThrottler{
    25  		maxProcessingMsgsPerNode: maxProcessingMsgsPerNode,
    26  		awaitingAcquire:          make(map[ids.NodeID]chan struct{}),
    27  		nodeToNumProcessingMsgs:  make(map[ids.NodeID]uint64),
    28  	}
    29  	return t, t.metrics.initialize(registerer)
    30  }
    31  
    32  // Rate-limits inbound messages based on the number of
    33  // messages from a given node that we're currently processing.
    34  type inboundMsgBufferThrottler struct {
    35  	lock    sync.Mutex
    36  	metrics inboundMsgBufferThrottlerMetrics
    37  	// Max number of messages currently processing from a
    38  	// given node. We will stop reading messages from a
    39  	// node until we're processing less than this many
    40  	// messages from the node.
    41  	// In this case, a message is "processing" if the corresponding
    42  	// call to Acquire() has returned or is about to return,
    43  	// but the corresponding call to Release() has not happened.
    44  	// TODO: Different values for validators / non-validators?
    45  	maxProcessingMsgsPerNode uint64
    46  	// Node ID --> Number of messages from this node we're currently processing.
    47  	// Must only be accessed when [lock] is held.
    48  	nodeToNumProcessingMsgs map[ids.NodeID]uint64
    49  	// Node ID --> Channel, when closed
    50  	// causes a goroutine waiting in Acquire to return.
    51  	// Must only be accessed when [lock] is held.
    52  	awaitingAcquire map[ids.NodeID]chan struct{}
    53  }
    54  
    55  // Acquire returns when we've acquired space on the inbound message
    56  // buffer so that we can read a message from [nodeID].
    57  // The returned release function must be called (!) when done processing the message
    58  // (or when we give up trying to read the message.)
    59  //
    60  // invariant: There should be a maximum of 1 blocking call to Acquire for a
    61  // given nodeID. Callers must enforce this invariant.
    62  func (t *inboundMsgBufferThrottler) Acquire(ctx context.Context, nodeID ids.NodeID) ReleaseFunc {
    63  	startTime := time.Now()
    64  	defer func() {
    65  		t.metrics.acquireLatency.Observe(float64(time.Since(startTime)))
    66  	}()
    67  
    68  	t.lock.Lock()
    69  	if t.nodeToNumProcessingMsgs[nodeID] < t.maxProcessingMsgsPerNode {
    70  		t.nodeToNumProcessingMsgs[nodeID]++
    71  		t.lock.Unlock()
    72  		return func() {
    73  			t.release(nodeID)
    74  		}
    75  	}
    76  
    77  	// We're currently processing the maximum number of
    78  	// messages from [nodeID]. Wait until we've finished
    79  	// processing some messages from [nodeID].
    80  	// [closeOnAcquireChan] will be closed inside Release()
    81  	// when we've acquired space on the inbound message buffer
    82  	// for this message.
    83  	closeOnAcquireChan := make(chan struct{})
    84  	t.awaitingAcquire[nodeID] = closeOnAcquireChan
    85  	t.lock.Unlock()
    86  	t.metrics.awaitingAcquire.Inc()
    87  	defer t.metrics.awaitingAcquire.Dec()
    88  
    89  	var releaseFunc ReleaseFunc
    90  	select {
    91  	case <-closeOnAcquireChan:
    92  		t.lock.Lock()
    93  		t.nodeToNumProcessingMsgs[nodeID]++
    94  		releaseFunc = func() {
    95  			t.release(nodeID)
    96  		}
    97  	case <-ctx.Done():
    98  		t.lock.Lock()
    99  		delete(t.awaitingAcquire, nodeID)
   100  		releaseFunc = noopRelease
   101  	}
   102  
   103  	t.lock.Unlock()
   104  	return releaseFunc
   105  }
   106  
   107  // release marks that we've finished processing a message from [nodeID]
   108  // and can release the space it took on the inbound message buffer.
   109  func (t *inboundMsgBufferThrottler) release(nodeID ids.NodeID) {
   110  	t.lock.Lock()
   111  	defer t.lock.Unlock()
   112  
   113  	t.nodeToNumProcessingMsgs[nodeID]--
   114  	if t.nodeToNumProcessingMsgs[nodeID] == 0 {
   115  		delete(t.nodeToNumProcessingMsgs, nodeID)
   116  	}
   117  
   118  	// If we're waiting to acquire space on the inbound message
   119  	// buffer for messages from [nodeID], allow it to proceed
   120  	// (i.e. for its call to Acquire to return.)
   121  	if waiting, ok := t.awaitingAcquire[nodeID]; ok {
   122  		close(waiting)
   123  		delete(t.awaitingAcquire, nodeID)
   124  	}
   125  }
   126  
   127  type inboundMsgBufferThrottlerMetrics struct {
   128  	acquireLatency  metric.Averager
   129  	awaitingAcquire prometheus.Gauge
   130  }
   131  
   132  func (m *inboundMsgBufferThrottlerMetrics) initialize(reg prometheus.Registerer) error {
   133  	errs := wrappers.Errs{}
   134  	m.acquireLatency = metric.NewAveragerWithErrs(
   135  		"buffer_throttler_inbound_acquire_latency",
   136  		"average time (in ns) to get space on the inbound message buffer",
   137  		reg,
   138  		&errs,
   139  	)
   140  	m.awaitingAcquire = prometheus.NewGauge(prometheus.GaugeOpts{
   141  		Name: "buffer_throttler_inbound_awaiting_acquire",
   142  		Help: "Number of inbound messages waiting to take space on the inbound message buffer",
   143  	})
   144  	errs.Add(
   145  		reg.Register(m.awaitingAcquire),
   146  	)
   147  	return errs.Err
   148  }