github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/distsql/inbound_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package distsql 12 13 import ( 14 "context" 15 "net" 16 "testing" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/base" 20 "github.com/cockroachdb/cockroach/pkg/roachpb" 21 "github.com/cockroachdb/cockroach/pkg/rpc" 22 "github.com/cockroachdb/cockroach/pkg/rpc/nodedialer" 23 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 24 "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 25 "github.com/cockroachdb/cockroach/pkg/sql/execinfrapb" 26 "github.com/cockroachdb/cockroach/pkg/sql/flowinfra" 27 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 28 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 29 "github.com/cockroachdb/cockroach/pkg/sql/types" 30 "github.com/cockroachdb/cockroach/pkg/testutils" 31 "github.com/cockroachdb/cockroach/pkg/testutils/distsqlutils" 32 "github.com/cockroachdb/cockroach/pkg/util" 33 "github.com/cockroachdb/cockroach/pkg/util/hlc" 34 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 35 "github.com/cockroachdb/cockroach/pkg/util/netutil" 36 "github.com/cockroachdb/cockroach/pkg/util/stop" 37 "github.com/cockroachdb/errors" 38 "github.com/stretchr/testify/require" 39 ) 40 41 // staticAddressResolver maps execinfra.StaticNodeID to the given address. 42 func staticAddressResolver(addr net.Addr) nodedialer.AddressResolver { 43 return func(nodeID roachpb.NodeID) (net.Addr, error) { 44 if nodeID == execinfra.StaticNodeID { 45 return addr, nil 46 } 47 return nil, errors.Errorf("node %d not found", nodeID) 48 } 49 } 50 51 // TestOutboxInboundStreamIntegration verifies that if an inbound stream gets 52 // a draining status from its consumer, that status is propagated back to the 53 // outbox and there are no goroutine leaks. 54 func TestOutboxInboundStreamIntegration(t *testing.T) { 55 defer leaktest.AfterTest(t)() 56 ctx := context.Background() 57 stopper := stop.NewStopper() 58 defer stopper.Stop(ctx) 59 st := cluster.MakeTestingClusterSettings() 60 mt := execinfra.MakeDistSQLMetrics(time.Hour /* histogramWindow */) 61 srv := NewServer( 62 ctx, 63 execinfra.ServerConfig{ 64 Settings: st, 65 Stopper: stopper, 66 Metrics: &mt, 67 NodeID: base.TestingIDContainer, 68 }, 69 ) 70 71 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 72 rpcContext := rpc.NewInsecureTestingContext(clock, stopper) 73 74 // We're going to serve multiple node IDs with that one context. Disable node ID checks. 75 rpcContext.TestingAllowNamedRPCToAnonymousServer = true 76 77 rpcSrv := rpc.NewServer(rpcContext) 78 defer rpcSrv.Stop() 79 80 execinfrapb.RegisterDistSQLServer(rpcSrv, srv) 81 ln, err := netutil.ListenAndServeGRPC(stopper, rpcSrv, util.IsolatedTestAddr) 82 if err != nil { 83 t.Fatal(err) 84 } 85 86 // The outbox uses this stopper to run a goroutine. 87 outboxStopper := stop.NewStopper() 88 flowCtx := execinfra.FlowCtx{ 89 Cfg: &execinfra.ServerConfig{ 90 Settings: st, 91 NodeDialer: nodedialer.New(rpcContext, staticAddressResolver(ln.Addr())), 92 Stopper: outboxStopper, 93 }, 94 } 95 96 streamID := execinfrapb.StreamID(1) 97 outbox := flowinfra.NewOutbox(&flowCtx, execinfra.StaticNodeID, execinfrapb.FlowID{}, streamID) 98 outbox.Init(sqlbase.OneIntCol) 99 100 // WaitGroup for the outbox and inbound stream. If the WaitGroup is done, no 101 // goroutines were leaked. Grab the flow's waitGroup to avoid a copy warning. 102 f := &flowinfra.FlowBase{} 103 wg := f.GetWaitGroup() 104 105 // Use RegisterFlow to register our consumer, which we will control. 106 consumer := distsqlutils.NewRowBuffer(sqlbase.OneIntCol, nil /* rows */, distsqlutils.RowBufferArgs{}) 107 connectionInfo := map[execinfrapb.StreamID]*flowinfra.InboundStreamInfo{ 108 streamID: flowinfra.NewInboundStreamInfo( 109 flowinfra.RowInboundStreamHandler{RowReceiver: consumer}, 110 wg, 111 ), 112 } 113 // Add to the WaitGroup counter for the inbound stream. 114 wg.Add(1) 115 require.NoError( 116 t, 117 srv.flowRegistry.RegisterFlow(ctx, execinfrapb.FlowID{}, f, connectionInfo, time.Hour /* timeout */), 118 ) 119 120 outbox.Start(ctx, wg, func() {}) 121 122 // Put the consumer in draining mode, this should propagate all the way back 123 // from the inbound stream to the outbox when it attempts to Push a row 124 // below. 125 consumer.ConsumerDone() 126 127 row := sqlbase.EncDatumRow{sqlbase.DatumToEncDatum(types.Int, tree.NewDInt(tree.DInt(0)))} 128 129 // Now push a row to the outbox's RowChannel and expect the consumer status 130 // returned to be DrainRequested. This is wrapped in a SucceedsSoon because 131 // the write to the row channel is asynchronous wrt the outbox sending the 132 // row and getting back the updated consumer status. 133 testutils.SucceedsSoon(t, func() error { 134 if cs := outbox.Push(row, nil /* meta */); cs != execinfra.DrainRequested { 135 return errors.Errorf("unexpected consumer status %s", cs) 136 } 137 return nil 138 }) 139 140 // As a producer, we are now required to call ProducerDone after draining. We 141 // do so now to simulate the fact that we have no more rows or metadata to 142 // send. 143 outbox.ProducerDone() 144 145 // Both the outbox and the inbound stream should exit. 146 wg.Wait() 147 148 // Wait for outstanding tasks to complete. Specifically, we are waiting for 149 // the outbox's drain signal listener to return. 150 outboxStopper.Quiesce(ctx) 151 }