github.com/lzy4123/fabric@v2.1.1+incompatible/orderer/common/cluster/connections_test.go (about) 1 /* 2 Copyright IBM Corp. 2017 All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package cluster_test 8 9 import ( 10 "sync" 11 "testing" 12 13 "github.com/hyperledger/fabric/common/metrics/disabled" 14 "github.com/hyperledger/fabric/orderer/common/cluster" 15 "github.com/hyperledger/fabric/orderer/common/cluster/mocks" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/mock" 18 "google.golang.org/grpc" 19 ) 20 21 func TestConcurrentConnections(t *testing.T) { 22 // Scenario: Have 100 goroutines try to create a connection together at the same time, 23 // wait until one of them succeeds, and then wait until they all return, 24 // and also ensure they all return the same connection reference 25 n := 100 26 var wg sync.WaitGroup 27 wg.Add(n) 28 dialer := &mocks.SecureDialer{} 29 conn := &grpc.ClientConn{} 30 dialer.On("Dial", mock.Anything, mock.Anything).Return(conn, nil) 31 connStore := cluster.NewConnectionStore(dialer, &disabled.Gauge{}) 32 connect := func() { 33 defer wg.Done() 34 conn2, err := connStore.Connection("", nil) 35 assert.NoError(t, err) 36 assert.True(t, conn2 == conn) 37 } 38 for i := 0; i < n; i++ { 39 go connect() 40 } 41 wg.Wait() 42 dialer.AssertNumberOfCalls(t, "Dial", 1) 43 } 44 45 type connectionMapperSpy struct { 46 lookupDelay chan struct{} 47 lookupInvoked chan struct{} 48 cluster.ConnectionMapper 49 } 50 51 func (cms *connectionMapperSpy) Lookup(cert []byte) (*grpc.ClientConn, bool) { 52 // Signal that Lookup() has been invoked 53 cms.lookupInvoked <- struct{}{} 54 // Wait for the main test to signal to advance. 55 // This is needed because we need to ensure that all instances 56 // of the connectionMapperSpy invoked Lookup() 57 <-cms.lookupDelay 58 return cms.ConnectionMapper.Lookup(cert) 59 } 60 61 func TestConcurrentLookupMiss(t *testing.T) { 62 // Scenario: 2 concurrent connection attempts are made, 63 // and the first 2 Lookup operations are delayed, 64 // which makes the connection store attempt to connect 65 // at the same time twice. 66 // A single connection should be created regardless. 67 68 dialer := &mocks.SecureDialer{} 69 conn := &grpc.ClientConn{} 70 dialer.On("Dial", mock.Anything, mock.Anything).Return(conn, nil) 71 72 connStore := cluster.NewConnectionStore(dialer, &disabled.Gauge{}) 73 // Wrap the connection mapping with a spy that intercepts Lookup() invocations 74 spy := &connectionMapperSpy{ 75 ConnectionMapper: connStore.Connections, 76 lookupDelay: make(chan struct{}, 2), 77 lookupInvoked: make(chan struct{}, 2), 78 } 79 connStore.Connections = spy 80 81 var goroutinesExited sync.WaitGroup 82 goroutinesExited.Add(2) 83 84 for i := 0; i < 2; i++ { 85 go func() { 86 defer goroutinesExited.Done() 87 conn2, err := connStore.Connection("", nil) 88 assert.NoError(t, err) 89 // Ensure all calls for Connection() return the same reference 90 // of the gRPC connection. 91 assert.True(t, conn2 == conn) 92 }() 93 } 94 // Wait for the Lookup() to be invoked by both 95 // goroutines 96 <-spy.lookupInvoked 97 <-spy.lookupInvoked 98 // Signal the goroutines to finish the Lookup() invocations 99 spy.lookupDelay <- struct{}{} 100 spy.lookupDelay <- struct{}{} 101 // Close the channel so that subsequent Lookup() operations won't be blocked 102 close(spy.lookupDelay) 103 // Wait for all goroutines to exit 104 goroutinesExited.Wait() 105 }