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  }