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  }