github.com/MetalBlockchain/metalgo@v1.11.9/network/peer/message_queue.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package peer
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/message"
    14  	"github.com/MetalBlockchain/metalgo/network/throttling"
    15  	"github.com/MetalBlockchain/metalgo/utils/buffer"
    16  	"github.com/MetalBlockchain/metalgo/utils/logging"
    17  )
    18  
    19  const initialQueueSize = 64
    20  
    21  var (
    22  	_ MessageQueue = (*throttledMessageQueue)(nil)
    23  	_ MessageQueue = (*blockingMessageQueue)(nil)
    24  )
    25  
    26  type SendFailedCallback interface {
    27  	SendFailed(message.OutboundMessage)
    28  }
    29  
    30  type SendFailedFunc func(message.OutboundMessage)
    31  
    32  func (f SendFailedFunc) SendFailed(msg message.OutboundMessage) {
    33  	f(msg)
    34  }
    35  
    36  type MessageQueue interface {
    37  	// Push attempts to add the message to the queue. If the context is
    38  	// canceled, then pushing the message will return `false` and the message
    39  	// will not be added to the queue.
    40  	Push(ctx context.Context, msg message.OutboundMessage) bool
    41  
    42  	// Pop blocks until a message is available and then returns the message. If
    43  	// the queue is closed, then `false` is returned.
    44  	Pop() (message.OutboundMessage, bool)
    45  
    46  	// PopNow attempts to return a message without blocking. If a message is not
    47  	// available or the queue is closed, then `false` is returned.
    48  	PopNow() (message.OutboundMessage, bool)
    49  
    50  	// Close empties the queue and prevents further messages from being pushed
    51  	// onto it. After calling close once, future calls to close will do nothing.
    52  	Close()
    53  }
    54  
    55  type throttledMessageQueue struct {
    56  	onFailed SendFailedCallback
    57  	// [id] of the peer we're sending messages to
    58  	id                   ids.NodeID
    59  	log                  logging.Logger
    60  	outboundMsgThrottler throttling.OutboundMsgThrottler
    61  
    62  	// Signalled when a message is added to the queue and when Close() is
    63  	// called.
    64  	cond *sync.Cond
    65  
    66  	// closed flags whether the send queue has been closed.
    67  	// [cond.L] must be held while accessing [closed].
    68  	closed bool
    69  
    70  	// queue of the messages
    71  	// [cond.L] must be held while accessing [queue].
    72  	queue buffer.Deque[message.OutboundMessage]
    73  }
    74  
    75  func NewThrottledMessageQueue(
    76  	onFailed SendFailedCallback,
    77  	id ids.NodeID,
    78  	log logging.Logger,
    79  	outboundMsgThrottler throttling.OutboundMsgThrottler,
    80  ) MessageQueue {
    81  	return &throttledMessageQueue{
    82  		onFailed:             onFailed,
    83  		id:                   id,
    84  		log:                  log,
    85  		outboundMsgThrottler: outboundMsgThrottler,
    86  		cond:                 sync.NewCond(&sync.Mutex{}),
    87  		queue:                buffer.NewUnboundedDeque[message.OutboundMessage](initialQueueSize),
    88  	}
    89  }
    90  
    91  func (q *throttledMessageQueue) Push(ctx context.Context, msg message.OutboundMessage) bool {
    92  	if err := ctx.Err(); err != nil {
    93  		q.log.Debug(
    94  			"dropping outgoing message",
    95  			zap.Stringer("messageOp", msg.Op()),
    96  			zap.Stringer("nodeID", q.id),
    97  			zap.Error(err),
    98  		)
    99  		q.onFailed.SendFailed(msg)
   100  		return false
   101  	}
   102  
   103  	// Acquire space on the outbound message queue, or drop [msg] if we can't.
   104  	if !q.outboundMsgThrottler.Acquire(msg, q.id) {
   105  		q.log.Debug(
   106  			"dropping outgoing message",
   107  			zap.String("reason", "rate-limiting"),
   108  			zap.Stringer("messageOp", msg.Op()),
   109  			zap.Stringer("nodeID", q.id),
   110  		)
   111  		q.onFailed.SendFailed(msg)
   112  		return false
   113  	}
   114  
   115  	// Invariant: must call q.outboundMsgThrottler.Release(msg, q.id) when [msg]
   116  	// is popped or, if this queue closes before [msg] is popped, when this
   117  	// queue closes.
   118  
   119  	q.cond.L.Lock()
   120  	defer q.cond.L.Unlock()
   121  
   122  	if q.closed {
   123  		q.log.Debug(
   124  			"dropping outgoing message",
   125  			zap.String("reason", "closed queue"),
   126  			zap.Stringer("messageOp", msg.Op()),
   127  			zap.Stringer("nodeID", q.id),
   128  		)
   129  		q.outboundMsgThrottler.Release(msg, q.id)
   130  		q.onFailed.SendFailed(msg)
   131  		return false
   132  	}
   133  
   134  	q.queue.PushRight(msg)
   135  	q.cond.Signal()
   136  	return true
   137  }
   138  
   139  func (q *throttledMessageQueue) Pop() (message.OutboundMessage, bool) {
   140  	q.cond.L.Lock()
   141  	defer q.cond.L.Unlock()
   142  
   143  	for {
   144  		if q.closed {
   145  			return nil, false
   146  		}
   147  		if q.queue.Len() > 0 {
   148  			// There is a message
   149  			break
   150  		}
   151  		// Wait until there is a message
   152  		q.cond.Wait()
   153  	}
   154  
   155  	return q.pop(), true
   156  }
   157  
   158  func (q *throttledMessageQueue) PopNow() (message.OutboundMessage, bool) {
   159  	q.cond.L.Lock()
   160  	defer q.cond.L.Unlock()
   161  
   162  	if q.closed || q.queue.Len() == 0 {
   163  		// There isn't a message
   164  		return nil, false
   165  	}
   166  
   167  	return q.pop(), true
   168  }
   169  
   170  func (q *throttledMessageQueue) pop() message.OutboundMessage {
   171  	msg, _ := q.queue.PopLeft()
   172  
   173  	q.outboundMsgThrottler.Release(msg, q.id)
   174  	return msg
   175  }
   176  
   177  func (q *throttledMessageQueue) Close() {
   178  	q.cond.L.Lock()
   179  	defer q.cond.L.Unlock()
   180  
   181  	if q.closed {
   182  		return
   183  	}
   184  
   185  	q.closed = true
   186  
   187  	for q.queue.Len() > 0 {
   188  		msg, _ := q.queue.PopLeft()
   189  		q.outboundMsgThrottler.Release(msg, q.id)
   190  		q.onFailed.SendFailed(msg)
   191  	}
   192  	q.queue = nil
   193  
   194  	q.cond.Broadcast()
   195  }
   196  
   197  type blockingMessageQueue struct {
   198  	onFailed SendFailedCallback
   199  	log      logging.Logger
   200  
   201  	closeOnce   sync.Once
   202  	closingLock sync.RWMutex
   203  	closing     chan struct{}
   204  
   205  	// queue of the messages
   206  	queue chan message.OutboundMessage
   207  }
   208  
   209  func NewBlockingMessageQueue(
   210  	onFailed SendFailedCallback,
   211  	log logging.Logger,
   212  	bufferSize int,
   213  ) MessageQueue {
   214  	return &blockingMessageQueue{
   215  		onFailed: onFailed,
   216  		log:      log,
   217  
   218  		closing: make(chan struct{}),
   219  		queue:   make(chan message.OutboundMessage, bufferSize),
   220  	}
   221  }
   222  
   223  func (q *blockingMessageQueue) Push(ctx context.Context, msg message.OutboundMessage) bool {
   224  	q.closingLock.RLock()
   225  	defer q.closingLock.RUnlock()
   226  
   227  	ctxDone := ctx.Done()
   228  	select {
   229  	case <-q.closing:
   230  		q.log.Debug(
   231  			"dropping message",
   232  			zap.String("reason", "closed queue"),
   233  			zap.Stringer("messageOp", msg.Op()),
   234  		)
   235  		q.onFailed.SendFailed(msg)
   236  		return false
   237  	case <-ctxDone:
   238  		q.log.Debug(
   239  			"dropping message",
   240  			zap.String("reason", "cancelled context"),
   241  			zap.Stringer("messageOp", msg.Op()),
   242  		)
   243  		q.onFailed.SendFailed(msg)
   244  		return false
   245  	default:
   246  	}
   247  
   248  	select {
   249  	case q.queue <- msg:
   250  		return true
   251  	case <-ctxDone:
   252  		q.log.Debug(
   253  			"dropping message",
   254  			zap.String("reason", "cancelled context"),
   255  			zap.Stringer("messageOp", msg.Op()),
   256  		)
   257  		q.onFailed.SendFailed(msg)
   258  		return false
   259  	case <-q.closing:
   260  		q.log.Debug(
   261  			"dropping message",
   262  			zap.String("reason", "closed queue"),
   263  			zap.Stringer("messageOp", msg.Op()),
   264  		)
   265  		q.onFailed.SendFailed(msg)
   266  		return false
   267  	}
   268  }
   269  
   270  func (q *blockingMessageQueue) Pop() (message.OutboundMessage, bool) {
   271  	select {
   272  	case msg := <-q.queue:
   273  		return msg, true
   274  	case <-q.closing:
   275  		return nil, false
   276  	}
   277  }
   278  
   279  func (q *blockingMessageQueue) PopNow() (message.OutboundMessage, bool) {
   280  	select {
   281  	case msg := <-q.queue:
   282  		return msg, true
   283  	default:
   284  		return nil, false
   285  	}
   286  }
   287  
   288  func (q *blockingMessageQueue) Close() {
   289  	q.closeOnce.Do(func() {
   290  		close(q.closing)
   291  
   292  		q.closingLock.Lock()
   293  		defer q.closingLock.Unlock()
   294  
   295  		for {
   296  			select {
   297  			case msg := <-q.queue:
   298  				q.onFailed.SendFailed(msg)
   299  			default:
   300  				return
   301  			}
   302  		}
   303  	})
   304  }