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 }