github.com/defanghe/fabric@v2.1.1+incompatible/gossip/comm/conn.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package comm 8 9 import ( 10 "context" 11 "sync" 12 13 proto "github.com/hyperledger/fabric-protos-go/gossip" 14 "github.com/hyperledger/fabric/gossip/common" 15 "github.com/hyperledger/fabric/gossip/metrics" 16 "github.com/hyperledger/fabric/gossip/protoext" 17 "github.com/hyperledger/fabric/gossip/util" 18 "github.com/pkg/errors" 19 "google.golang.org/grpc" 20 ) 21 22 type handler func(message *protoext.SignedGossipMessage) 23 24 type blockingBehavior bool 25 26 const ( 27 blockingSend = blockingBehavior(true) 28 nonBlockingSend = blockingBehavior(false) 29 ) 30 31 type connFactory interface { 32 createConnection(endpoint string, pkiID common.PKIidType) (*connection, error) 33 } 34 35 type connectionStore struct { 36 config ConnConfig 37 logger util.Logger // logger 38 isClosing bool // whether this connection store is shutting down 39 shutdownOnce sync.Once // ensure shutdown is only called once per connectionStore 40 connFactory connFactory // creates a connection to remote peer 41 sync.RWMutex // synchronize access to shared variables 42 pki2Conn map[string]*connection // mapping between pkiID to connections 43 destinationLocks map[string]*sync.Mutex //mapping between pkiIDs and locks, 44 // used to prevent concurrent connection establishment to the same remote endpoint 45 } 46 47 func newConnStore(connFactory connFactory, logger util.Logger, config ConnConfig) *connectionStore { 48 return &connectionStore{ 49 connFactory: connFactory, 50 isClosing: false, 51 pki2Conn: make(map[string]*connection), 52 destinationLocks: make(map[string]*sync.Mutex), 53 logger: logger, 54 config: config, 55 } 56 } 57 58 func (cs *connectionStore) getConnection(peer *RemotePeer) (*connection, error) { 59 cs.RLock() 60 isClosing := cs.isClosing 61 cs.RUnlock() 62 63 if isClosing { 64 return nil, errors.Errorf("conn store is closing") 65 } 66 67 pkiID := peer.PKIID 68 endpoint := peer.Endpoint 69 70 cs.Lock() 71 destinationLock, hasConnected := cs.destinationLocks[string(pkiID)] 72 if !hasConnected { 73 destinationLock = &sync.Mutex{} 74 cs.destinationLocks[string(pkiID)] = destinationLock 75 } 76 cs.Unlock() 77 78 destinationLock.Lock() 79 80 cs.RLock() 81 conn, exists := cs.pki2Conn[string(pkiID)] 82 if exists { 83 cs.RUnlock() 84 destinationLock.Unlock() 85 return conn, nil 86 } 87 cs.RUnlock() 88 89 createdConnection, err := cs.connFactory.createConnection(endpoint, pkiID) 90 if err == nil { 91 cs.logger.Debugf("Created new connection to %s, %s", endpoint, pkiID) 92 } 93 94 destinationLock.Unlock() 95 96 cs.RLock() 97 isClosing = cs.isClosing 98 cs.RUnlock() 99 if isClosing { 100 return nil, errors.Errorf("conn store is closing") 101 } 102 103 cs.Lock() 104 delete(cs.destinationLocks, string(pkiID)) 105 defer cs.Unlock() 106 107 // check again, maybe someone connected to us during the connection creation? 108 conn, exists = cs.pki2Conn[string(pkiID)] 109 110 if exists { 111 if createdConnection != nil { 112 createdConnection.close() 113 } 114 return conn, nil 115 } 116 117 // no one connected to us AND we failed connecting! 118 if err != nil { 119 return nil, err 120 } 121 122 // At this point we know the latest PKI-ID of the remote peer. 123 // Perhaps we are trying to connect to a peer that changed its PKI-ID. 124 // Ensure that we are not already connected to that PKI-ID, 125 // and if so - close the old connection in order to keep the latest one. 126 // We keep the latest one because the remote peer is going to close 127 // our old connection anyway once it sensed this connection handshake. 128 if conn, exists := cs.pki2Conn[string(createdConnection.pkiID)]; exists { 129 conn.close() 130 } 131 132 // at this point in the code, we created a connection to a remote peer 133 conn = createdConnection 134 cs.pki2Conn[string(createdConnection.pkiID)] = conn 135 136 go conn.serviceConnection() 137 138 return conn, nil 139 } 140 141 func (cs *connectionStore) connNum() int { 142 cs.RLock() 143 defer cs.RUnlock() 144 return len(cs.pki2Conn) 145 } 146 147 func (cs *connectionStore) shutdown() { 148 cs.shutdownOnce.Do(func() { 149 cs.Lock() 150 cs.isClosing = true 151 152 for _, conn := range cs.pki2Conn { 153 conn.close() 154 } 155 cs.pki2Conn = make(map[string]*connection) 156 157 cs.Unlock() 158 }) 159 } 160 161 // onConnected closes any connection to the remote peer and creates a new connection object to it in order to have only 162 // one single bi-directional connection between a pair of peers 163 func (cs *connectionStore) onConnected(serverStream proto.Gossip_GossipStreamServer, 164 connInfo *protoext.ConnectionInfo, metrics *metrics.CommMetrics) *connection { 165 cs.Lock() 166 defer cs.Unlock() 167 168 if c, exists := cs.pki2Conn[string(connInfo.ID)]; exists { 169 c.close() 170 } 171 172 conn := newConnection(nil, nil, serverStream, metrics, cs.config) 173 conn.pkiID = connInfo.ID 174 conn.info = connInfo 175 conn.logger = cs.logger 176 cs.pki2Conn[string(connInfo.ID)] = conn 177 return conn 178 } 179 180 func (cs *connectionStore) closeConnByPKIid(pkiID common.PKIidType) { 181 cs.Lock() 182 defer cs.Unlock() 183 if conn, exists := cs.pki2Conn[string(pkiID)]; exists { 184 conn.close() 185 delete(cs.pki2Conn, string(pkiID)) 186 } 187 } 188 189 func newConnection(cl proto.GossipClient, c *grpc.ClientConn, s stream, metrics *metrics.CommMetrics, config ConnConfig) *connection { 190 connection := &connection{ 191 metrics: metrics, 192 outBuff: make(chan *msgSending, config.SendBuffSize), 193 cl: cl, 194 conn: c, 195 gossipStream: s, 196 stopChan: make(chan struct{}, 1), 197 recvBuffSize: config.RecvBuffSize, 198 } 199 return connection 200 } 201 202 // ConnConfig is the configuration required to initialize a new conn 203 type ConnConfig struct { 204 RecvBuffSize int 205 SendBuffSize int 206 } 207 208 type connection struct { 209 recvBuffSize int 210 metrics *metrics.CommMetrics 211 cancel context.CancelFunc 212 info *protoext.ConnectionInfo 213 outBuff chan *msgSending 214 logger util.Logger // logger 215 pkiID common.PKIidType // pkiID of the remote endpoint 216 handler handler // function to invoke upon a message reception 217 conn *grpc.ClientConn // gRPC connection to remote endpoint 218 cl proto.GossipClient // gRPC stub of remote endpoint 219 gossipStream stream // there can only be one 220 stopChan chan struct{} // a method to stop the server-side gRPC call from a different go-routine 221 stopOnce sync.Once // once to ensure close is called only once 222 } 223 224 func (conn *connection) close() { 225 conn.stopOnce.Do(func() { 226 close(conn.stopChan) 227 228 if conn.conn != nil { 229 conn.conn.Close() 230 } 231 232 if conn.cancel != nil { 233 conn.cancel() 234 } 235 }) 236 } 237 238 func (conn *connection) send(msg *protoext.SignedGossipMessage, onErr func(error), shouldBlock blockingBehavior) { 239 m := &msgSending{ 240 envelope: msg.Envelope, 241 onErr: onErr, 242 } 243 244 select { 245 case conn.outBuff <- m: 246 // room in channel, successfully sent message, nothing to do 247 case <-conn.stopChan: 248 conn.logger.Debugf("Aborting send() to %s because connection is closing", conn.info.Endpoint) 249 default: // did not send 250 if shouldBlock { 251 select { 252 case conn.outBuff <- m: // try again, and wait to send 253 case <-conn.stopChan: //stop blocking if the connection is closing 254 } 255 } else { 256 conn.metrics.BufferOverflow.Add(1) 257 conn.logger.Debugf("Buffer to %s overflowed, dropping message %s", conn.info.Endpoint, msg) 258 } 259 } 260 } 261 262 func (conn *connection) serviceConnection() error { 263 errChan := make(chan error, 1) 264 msgChan := make(chan *protoext.SignedGossipMessage, conn.recvBuffSize) 265 // Call stream.Recv() asynchronously in readFromStream(), 266 // and wait for either the Recv() call to end, 267 // or a signal to close the connection, which exits 268 // the method and makes the Recv() call to fail in the 269 // readFromStream() method 270 go conn.readFromStream(errChan, msgChan) 271 272 go conn.writeToStream() 273 274 for { 275 select { 276 case <-conn.stopChan: 277 conn.logger.Debug("Closing reading from stream") 278 return nil 279 case err := <-errChan: 280 return err 281 case msg := <-msgChan: 282 conn.handler(msg) 283 } 284 } 285 } 286 287 func (conn *connection) writeToStream() { 288 stream := conn.gossipStream 289 for { 290 select { 291 case m := <-conn.outBuff: 292 err := stream.Send(m.envelope) 293 if err != nil { 294 go m.onErr(err) 295 return 296 } 297 conn.metrics.SentMessages.Add(1) 298 case <-conn.stopChan: 299 conn.logger.Debug("Closing writing to stream") 300 return 301 } 302 } 303 } 304 305 func (conn *connection) readFromStream(errChan chan error, msgChan chan *protoext.SignedGossipMessage) { 306 stream := conn.gossipStream 307 for { 308 select { 309 case <-conn.stopChan: 310 return 311 default: 312 envelope, err := stream.Recv() 313 if err != nil { 314 errChan <- err 315 conn.logger.Debugf("Got error, aborting: %v", err) 316 return 317 } 318 conn.metrics.ReceivedMessages.Add(1) 319 msg, err := protoext.EnvelopeToGossipMessage(envelope) 320 if err != nil { 321 errChan <- err 322 conn.logger.Warningf("Got error, aborting: %v", err) 323 return 324 } 325 select { 326 case <-conn.stopChan: 327 case msgChan <- msg: 328 } 329 } 330 } 331 } 332 333 type msgSending struct { 334 envelope *proto.Envelope 335 onErr func(error) 336 } 337 338 //go:generate mockery -dir . -name MockStream -case underscore -output mocks/ 339 340 type MockStream interface { 341 proto.Gossip_GossipStreamClient 342 }