github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/pubsubmanager.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"context"
    10  	"sync"
    11  
    12  	"github.com/pkg/errors"
    13  	"go.uber.org/zap"
    14  
    15  	"github.com/iotexproject/iotex-core/blockchain/block"
    16  	"github.com/iotexproject/iotex-core/pkg/log"
    17  )
    18  
    19  type (
    20  	// PubSubManager is an interface which handles multi-thread publisher and subscribers
    21  	PubSubManager interface {
    22  		Start(ctx context.Context) error
    23  		Stop(ctx context.Context) error
    24  		AddBlockListener(BlockCreationSubscriber) error
    25  		RemoveBlockListener(BlockCreationSubscriber) error
    26  		SendBlockToSubscribers(*block.Block)
    27  	}
    28  
    29  	pubSubElem struct {
    30  		listener          BlockCreationSubscriber
    31  		pendingBlksBuffer chan *block.Block // buffered channel for storing the pending blocks
    32  		cancel            chan interface{}  // cancel channel to end the handler thread
    33  	}
    34  
    35  	pubSub struct {
    36  		lock                 sync.RWMutex
    37  		blocklisteners       []*pubSubElem
    38  		pendingBlkBufferSize uint64
    39  	}
    40  )
    41  
    42  // NewPubSub creates new pubSub struct with buffersize for pendingBlock buffer channel
    43  func NewPubSub(bufferSize uint64) PubSubManager {
    44  	return &pubSub{
    45  		blocklisteners:       make([]*pubSubElem, 0),
    46  		pendingBlkBufferSize: bufferSize,
    47  	}
    48  }
    49  
    50  func (ps *pubSub) newSubscriber(s BlockCreationSubscriber) *pubSubElem {
    51  	pendingBlksChan := make(chan *block.Block, ps.pendingBlkBufferSize)
    52  	cancelChan := make(chan interface{})
    53  	return &pubSubElem{
    54  		listener:          s,
    55  		pendingBlksBuffer: pendingBlksChan,
    56  		cancel:            cancelChan,
    57  	}
    58  }
    59  
    60  // Start starts the pubsub manager
    61  func (ps *pubSub) Start(_ context.Context) error {
    62  	return nil
    63  }
    64  
    65  // AddBlockListener creates new pubSubElem subscriber and append it to blocklisteners
    66  func (ps *pubSub) AddBlockListener(s BlockCreationSubscriber) error {
    67  	sub := ps.newSubscriber(s)
    68  	// create subscriber handler thread to handle pending blocks
    69  	go ps.handler(sub)
    70  
    71  	ps.lock.Lock()
    72  	ps.blocklisteners = append(ps.blocklisteners, sub)
    73  	ps.lock.Unlock()
    74  	return nil
    75  }
    76  
    77  // RemoveBlockListener looks up blocklisteners and if exists, close the cancel channel and pop out the element
    78  func (ps *pubSub) RemoveBlockListener(s BlockCreationSubscriber) error {
    79  	ps.lock.Lock()
    80  	defer ps.lock.Unlock()
    81  	for i, elem := range ps.blocklisteners {
    82  		if elem.listener == s {
    83  			close(elem.cancel)
    84  			ps.blocklisteners[i] = nil
    85  			ps.blocklisteners = append(ps.blocklisteners[:i], ps.blocklisteners[i+1:]...)
    86  			log.L().Info("Successfully unsubscribe block creation.")
    87  			return nil
    88  		}
    89  	}
    90  	return errors.New("cannot find subscription")
    91  }
    92  
    93  // SendBlockToSubscribers sends block to every subscriber by using buffer channel
    94  func (ps *pubSub) SendBlockToSubscribers(blk *block.Block) {
    95  	ps.lock.RLock()
    96  	defer ps.lock.RUnlock()
    97  	for _, elem := range ps.blocklisteners {
    98  		elem.pendingBlksBuffer <- blk
    99  	}
   100  }
   101  
   102  // Stop stops the pubsub manager
   103  func (ps *pubSub) Stop(_ context.Context) error {
   104  	ps.lock.Lock()
   105  	defer ps.lock.Unlock()
   106  	for i, elem := range ps.blocklisteners {
   107  		close(elem.cancel)
   108  		log.L().Info("Successfully unsubscribe block creation.", zap.Int("listener", i))
   109  	}
   110  	ps.blocklisteners = nil
   111  	return nil
   112  }
   113  
   114  func (ps *pubSub) handler(sub *pubSubElem) {
   115  	for {
   116  		select {
   117  		case <-sub.cancel:
   118  			return
   119  		case blk := <-sub.pendingBlksBuffer:
   120  			if err := sub.listener.ReceiveBlock(blk); err != nil {
   121  				log.L().Error("Failed to handle new block.", zap.Error(err))
   122  			}
   123  		}
   124  	}
   125  }