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

     1  package api
     2  
     3  import (
     4  	"encoding/hex"
     5  	"sync"
     6  
     7  	"github.com/iotexproject/go-pkgs/cache/ttl"
     8  	"github.com/pkg/errors"
     9  	"go.uber.org/zap"
    10  
    11  	apitypes "github.com/iotexproject/iotex-core/api/types"
    12  	"github.com/iotexproject/iotex-core/blockchain/block"
    13  	"github.com/iotexproject/iotex-core/pkg/fastrand"
    14  	"github.com/iotexproject/iotex-core/pkg/log"
    15  )
    16  
    17  const (
    18  	_idSize  = 16
    19  	_idRetry = 1e6
    20  )
    21  
    22  var (
    23  	errorUnsupportedType = errors.New("type is unsupported")
    24  	errorCapacityReached = errors.New("capacity has been reached")
    25  	errListenerNotFound  = errors.New("subscription not found")
    26  )
    27  
    28  type (
    29  	// chainListener implements the Listener interface
    30  	chainListener struct {
    31  		maxCapacity int
    32  		streamMap   *ttl.Cache // all registered <Responder, chan error>
    33  		idGenerator *randID
    34  		mu          sync.Mutex
    35  	}
    36  )
    37  
    38  // NewChainListener returns a new blockchain chainListener
    39  func NewChainListener(c int) apitypes.Listener {
    40  	s, _ := ttl.NewCache(ttl.EvictOnErrorOption())
    41  	return &chainListener{
    42  		maxCapacity: c,
    43  		streamMap:   s,
    44  		idGenerator: newIDGenerator(_idSize),
    45  	}
    46  }
    47  
    48  // Start starts the chainListener
    49  func (cl *chainListener) Start() error {
    50  	return nil
    51  }
    52  
    53  // Stop stops the block chainListener
    54  func (cl *chainListener) Stop() error {
    55  	// notify all responders to exit
    56  	cl.streamMap.Range(func(_, value interface{}) error {
    57  		r, ok := value.(apitypes.Responder)
    58  		if !ok {
    59  			log.L().Error("streamMap stores a value which is not a Responder")
    60  			return errorUnsupportedType
    61  		}
    62  		r.Exit()
    63  		return nil
    64  	})
    65  	cl.streamMap.Reset()
    66  	return nil
    67  }
    68  
    69  // ReceiveBlock handles the block
    70  func (cl *chainListener) ReceiveBlock(blk *block.Block) error {
    71  	// pass the block to every responder
    72  	cl.streamMap.Range(func(key, value interface{}) error {
    73  		r, ok := value.(apitypes.Responder)
    74  		if !ok {
    75  			log.L().Error("streamMap stores a value which is not a Responder")
    76  			return errorUnsupportedType
    77  		}
    78  		err := r.Respond(key.(string), blk)
    79  		if err != nil {
    80  			log.L().Error("responder failed to process block", zap.Error(err))
    81  		}
    82  		return err
    83  	})
    84  	return nil
    85  }
    86  
    87  // AddResponder adds a new responder
    88  func (cl *chainListener) AddResponder(responder apitypes.Responder) (string, error) {
    89  	cl.mu.Lock()
    90  	defer cl.mu.Unlock()
    91  	if cl.streamMap.Count() >= cl.maxCapacity {
    92  		return "", errorCapacityReached
    93  	}
    94  
    95  	listenerID, i := "", 0
    96  	// An new id is assumed to be found, because combinations (2^62) is far larger than capacity
    97  	for ; i < _idRetry; i++ {
    98  		listenerID = cl.idGenerator.newID()
    99  		if _, exist := cl.streamMap.Get(listenerID); !exist {
   100  			break
   101  		}
   102  	}
   103  	if i == _idRetry {
   104  		return "", errors.New("No peer id is available")
   105  	}
   106  
   107  	cl.streamMap.Set(listenerID, responder)
   108  	return listenerID, nil
   109  }
   110  
   111  // RemoveResponder delete the responder
   112  func (cl *chainListener) RemoveResponder(listenerID string) (bool, error) {
   113  	cl.mu.Lock()
   114  	defer cl.mu.Unlock()
   115  	value, exist := cl.streamMap.Get(listenerID)
   116  	if !exist {
   117  		return false, errListenerNotFound
   118  	}
   119  	r, ok := value.(apitypes.Responder)
   120  	if !ok {
   121  		log.L().Error("streamMap stores a value which is not a Responder")
   122  		return false, errListenerNotFound
   123  	}
   124  	r.Exit()
   125  	return cl.streamMap.Delete(listenerID), nil
   126  }
   127  
   128  type randID struct {
   129  	length uint8
   130  }
   131  
   132  func newIDGenerator(length uint8) *randID {
   133  	return &randID{length: length}
   134  }
   135  
   136  func (id *randID) newID() string {
   137  	token := make([]byte, id.length)
   138  	fastrand.Read(token)
   139  	return "0x" + hex.EncodeToString(token)
   140  }