github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+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 t.Parallel() 23 // Scenario: Have 100 goroutines try to create a connection together at the same time, 24 // wait until one of them succeeds, and then wait until they all return, 25 // and also ensure they all return the same connection reference 26 n := 100 27 var wg sync.WaitGroup 28 wg.Add(n) 29 dialer := &mocks.SecureDialer{} 30 conn := &grpc.ClientConn{} 31 dialer.On("Dial", mock.Anything, mock.Anything).Return(conn, nil) 32 connStore := cluster.NewConnectionStore(dialer, &disabled.Gauge{}) 33 connect := func() { 34 defer wg.Done() 35 conn2, err := connStore.Connection("", nil) 36 assert.NoError(t, err) 37 assert.True(t, conn2 == conn) 38 } 39 for i := 0; i < n; i++ { 40 go connect() 41 } 42 wg.Wait() 43 dialer.AssertNumberOfCalls(t, "Dial", 1) 44 } 45 46 type connectionMapperSpy struct { 47 lookupDelay chan struct{} 48 lookupInvoked chan struct{} 49 cluster.ConnectionMapper 50 } 51 52 func (cms *connectionMapperSpy) Lookup(cert []byte) (*grpc.ClientConn, bool) { 53 // Signal that Lookup() has been invoked 54 cms.lookupInvoked <- struct{}{} 55 // Wait for the main test to signal to advance. 56 // This is needed because we need to ensure that all instances 57 // of the connectionMapperSpy invoked Lookup() 58 <-cms.lookupDelay 59 return cms.ConnectionMapper.Lookup(cert) 60 } 61 62 func TestConcurrentLookupMiss(t *testing.T) { 63 t.Parallel() 64 // Scenario: 2 concurrent connection attempts are made, 65 // and the first 2 Lookup operations are delayed, 66 // which makes the connection store attempt to connect 67 // at the same time twice. 68 // A single connection should be created regardless. 69 70 dialer := &mocks.SecureDialer{} 71 conn := &grpc.ClientConn{} 72 dialer.On("Dial", mock.Anything, mock.Anything).Return(conn, nil) 73 74 connStore := cluster.NewConnectionStore(dialer, &disabled.Gauge{}) 75 // Wrap the connection mapping with a spy that intercepts Lookup() invocations 76 spy := &connectionMapperSpy{ 77 ConnectionMapper: connStore.Connections, 78 lookupDelay: make(chan struct{}, 2), 79 lookupInvoked: make(chan struct{}, 2), 80 } 81 connStore.Connections = spy 82 83 var goroutinesExited sync.WaitGroup 84 goroutinesExited.Add(2) 85 86 for i := 0; i < 2; i++ { 87 go func() { 88 defer goroutinesExited.Done() 89 conn2, err := connStore.Connection("", nil) 90 assert.NoError(t, err) 91 // Ensure all calls for Connection() return the same reference 92 // of the gRPC connection. 93 assert.True(t, conn2 == conn) 94 }() 95 } 96 // Wait for the Lookup() to be invoked by both 97 // goroutines 98 <-spy.lookupInvoked 99 <-spy.lookupInvoked 100 // Signal the goroutines to finish the Lookup() invocations 101 spy.lookupDelay <- struct{}{} 102 spy.lookupDelay <- struct{}{} 103 // Close the channel so that subsequent Lookup() operations won't be blocked 104 close(spy.lookupDelay) 105 // Wait for all goroutines to exit 106 goroutinesExited.Wait() 107 }