github.com/ewagmig/fabric@v2.1.1+incompatible/gossip/state/payloads_buffer.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package state
     8  
     9  import (
    10  	"sync"
    11  	"sync/atomic"
    12  
    13  	proto "github.com/hyperledger/fabric-protos-go/gossip"
    14  	"github.com/hyperledger/fabric/common/metrics"
    15  	"github.com/hyperledger/fabric/gossip/util"
    16  )
    17  
    18  // PayloadsBuffer is used to store payloads into which used to
    19  // support payloads with blocks reordering according to the
    20  // sequence numbers. It also will provide the capability
    21  // to signal whenever expected block has arrived.
    22  type PayloadsBuffer interface {
    23  	// Adds new block into the buffer
    24  	Push(payload *proto.Payload)
    25  
    26  	// Returns next expected sequence number
    27  	Next() uint64
    28  
    29  	// Remove and return payload with given sequence number
    30  	Pop() *proto.Payload
    31  
    32  	// Get current buffer size
    33  	Size() int
    34  
    35  	// Channel to indicate event when new payload pushed with sequence
    36  	// number equal to the next expected value.
    37  	Ready() chan struct{}
    38  
    39  	Close()
    40  }
    41  
    42  // PayloadsBufferImpl structure to implement PayloadsBuffer interface
    43  // store inner state of available payloads and sequence numbers
    44  type PayloadsBufferImpl struct {
    45  	next uint64
    46  
    47  	buf map[uint64]*proto.Payload
    48  
    49  	readyChan chan struct{}
    50  
    51  	mutex sync.RWMutex
    52  
    53  	logger util.Logger
    54  }
    55  
    56  // NewPayloadsBuffer is factory function to create new payloads buffer
    57  func NewPayloadsBuffer(next uint64) PayloadsBuffer {
    58  	return &PayloadsBufferImpl{
    59  		buf:       make(map[uint64]*proto.Payload),
    60  		readyChan: make(chan struct{}, 1),
    61  		next:      next,
    62  		logger:    util.GetLogger(util.StateLogger, ""),
    63  	}
    64  }
    65  
    66  // Ready function returns the channel which indicates whenever expected
    67  // next block has arrived and one could safely pop out
    68  // next sequence of blocks
    69  func (b *PayloadsBufferImpl) Ready() chan struct{} {
    70  	return b.readyChan
    71  }
    72  
    73  // Push new payload into the buffer structure in case new arrived payload
    74  // sequence number is below the expected next block number payload will be
    75  // thrown away.
    76  // TODO return bool to indicate if payload was added or not, so that caller can log result.
    77  func (b *PayloadsBufferImpl) Push(payload *proto.Payload) {
    78  	b.mutex.Lock()
    79  	defer b.mutex.Unlock()
    80  
    81  	seqNum := payload.SeqNum
    82  
    83  	if seqNum < b.next || b.buf[seqNum] != nil {
    84  		b.logger.Debugf("Payload with sequence number = %d has been already processed", payload.SeqNum)
    85  		return
    86  	}
    87  
    88  	b.buf[seqNum] = payload
    89  
    90  	// Send notification that next sequence has arrived
    91  	if seqNum == b.next && len(b.readyChan) == 0 {
    92  		b.readyChan <- struct{}{}
    93  	}
    94  }
    95  
    96  // Next function provides the number of the next expected block
    97  func (b *PayloadsBufferImpl) Next() uint64 {
    98  	// Atomically read the value of the top sequence number
    99  	return atomic.LoadUint64(&b.next)
   100  }
   101  
   102  // Pop function extracts the payload according to the next expected block
   103  // number, if no next block arrived yet, function returns nil.
   104  func (b *PayloadsBufferImpl) Pop() *proto.Payload {
   105  	b.mutex.Lock()
   106  	defer b.mutex.Unlock()
   107  
   108  	result := b.buf[b.Next()]
   109  
   110  	if result != nil {
   111  		// If there is such sequence in the buffer need to delete it
   112  		delete(b.buf, b.Next())
   113  		// Increment next expect block index
   114  		atomic.AddUint64(&b.next, 1)
   115  
   116  		b.drainReadChannel()
   117  
   118  	}
   119  
   120  	return result
   121  }
   122  
   123  // drainReadChannel empties ready channel in case last
   124  // payload has been poped up and there are still awaiting
   125  // notifications in the channel
   126  func (b *PayloadsBufferImpl) drainReadChannel() {
   127  	if len(b.buf) == 0 {
   128  		for {
   129  			if len(b.readyChan) > 0 {
   130  				<-b.readyChan
   131  			} else {
   132  				break
   133  			}
   134  		}
   135  	}
   136  }
   137  
   138  // Size returns current number of payloads stored within buffer
   139  func (b *PayloadsBufferImpl) Size() int {
   140  	b.mutex.RLock()
   141  	defer b.mutex.RUnlock()
   142  	return len(b.buf)
   143  }
   144  
   145  // Close cleanups resources and channels in maintained
   146  func (b *PayloadsBufferImpl) Close() {
   147  	close(b.readyChan)
   148  }
   149  
   150  type metricsBuffer struct {
   151  	PayloadsBuffer
   152  	sizeMetrics metrics.Gauge
   153  	chainID     string
   154  }
   155  
   156  func (mb *metricsBuffer) Push(payload *proto.Payload) {
   157  	mb.PayloadsBuffer.Push(payload)
   158  	mb.reportSize()
   159  }
   160  
   161  func (mb *metricsBuffer) Pop() *proto.Payload {
   162  	pl := mb.PayloadsBuffer.Pop()
   163  	mb.reportSize()
   164  	return pl
   165  }
   166  
   167  func (mb *metricsBuffer) reportSize() {
   168  	mb.sizeMetrics.With("channel", mb.chainID).Set(float64(mb.Size()))
   169  }