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 }