github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/flowinfra/inbound.go (about)

     1  // Copyright 2016 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 flowinfra
    12  
    13  import (
    14  	"context"
    15  	"io"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/errors"
    25  )
    26  
    27  // InboundStreamHandler is a handler of an inbound stream.
    28  type InboundStreamHandler interface {
    29  	// run is called once a FlowStream RPC is handled and a stream is obtained to
    30  	// make this stream accessible to the rest of the flow.
    31  	Run(
    32  		ctx context.Context, stream execinfrapb.DistSQL_FlowStreamServer, firstMsg *execinfrapb.ProducerMessage, f *FlowBase,
    33  	) error
    34  	// timeout is called with an error, which results in the teardown of the
    35  	// stream strategy with the given error.
    36  	// WARNING: timeout may block.
    37  	Timeout(err error)
    38  }
    39  
    40  // RowInboundStreamHandler is an InboundStreamHandler for the row based flow.
    41  // It is exported since it is the default for the flow infrastructure.
    42  type RowInboundStreamHandler struct {
    43  	execinfra.RowReceiver
    44  }
    45  
    46  var _ InboundStreamHandler = RowInboundStreamHandler{}
    47  
    48  // Run is part of the InboundStreamHandler interface.
    49  func (s RowInboundStreamHandler) Run(
    50  	ctx context.Context,
    51  	stream execinfrapb.DistSQL_FlowStreamServer,
    52  	firstMsg *execinfrapb.ProducerMessage,
    53  	f *FlowBase,
    54  ) error {
    55  	return processInboundStream(ctx, stream, firstMsg, s.RowReceiver, f)
    56  }
    57  
    58  // Timeout is part of the InboundStreamHandler interface.
    59  func (s RowInboundStreamHandler) Timeout(err error) {
    60  	s.Push(
    61  		nil, /* row */
    62  		&execinfrapb.ProducerMetadata{Err: err},
    63  	)
    64  	s.ProducerDone()
    65  }
    66  
    67  // processInboundStream receives rows from a DistSQL_FlowStreamServer and sends
    68  // them to a RowReceiver. Optionally processes an initial StreamMessage that was
    69  // already received (because the first message contains the flow and stream IDs,
    70  // it needs to be received before we can get here).
    71  func processInboundStream(
    72  	ctx context.Context,
    73  	stream execinfrapb.DistSQL_FlowStreamServer,
    74  	firstMsg *execinfrapb.ProducerMessage,
    75  	dst execinfra.RowReceiver,
    76  	f *FlowBase,
    77  ) error {
    78  
    79  	err := processInboundStreamHelper(ctx, stream, firstMsg, dst, f)
    80  
    81  	// err, if set, will also be propagated to the producer
    82  	// as the last record that the producer gets.
    83  	if err != nil {
    84  		log.VEventf(ctx, 1, "inbound stream error: %s", err)
    85  		return err
    86  	}
    87  	log.VEventf(ctx, 1, "inbound stream done")
    88  	// We are now done. The producer, if it's still around, will receive an EOF
    89  	// error over its side of the stream.
    90  	return nil
    91  }
    92  
    93  func processInboundStreamHelper(
    94  	ctx context.Context,
    95  	stream execinfrapb.DistSQL_FlowStreamServer,
    96  	firstMsg *execinfrapb.ProducerMessage,
    97  	dst execinfra.RowReceiver,
    98  	f *FlowBase,
    99  ) error {
   100  	draining := false
   101  	var sd StreamDecoder
   102  
   103  	sendErrToConsumer := func(err error) {
   104  		if err != nil {
   105  			dst.Push(nil, &execinfrapb.ProducerMetadata{Err: err})
   106  		}
   107  		dst.ProducerDone()
   108  	}
   109  
   110  	if firstMsg != nil {
   111  		if res := processProducerMessage(
   112  			ctx, stream, dst, &sd, &draining, firstMsg,
   113  		); res.err != nil || res.consumerClosed {
   114  			sendErrToConsumer(res.err)
   115  			return res.err
   116  		}
   117  	}
   118  
   119  	// There's two goroutines involved in handling the RPC - the current one (the
   120  	// "parent"), which is watching for context cancellation, and a "reader" one
   121  	// that receives messages from the stream. This is all because a stream.Recv()
   122  	// call doesn't react to context cancellation. The idea is that, if the parent
   123  	// detects a canceled context, it will return from this RPC handler, which
   124  	// will cause the stream to be closed. Because the parent cannot wait for the
   125  	// reader to finish (that being the whole point of the different goroutines),
   126  	// the reader sending an error to the parent might race with the parent
   127  	// finishing. In that case, nobody cares about the reader anymore and so its
   128  	// result channel is buffered.
   129  	errChan := make(chan error, 1)
   130  
   131  	f.GetWaitGroup().Add(1)
   132  	go func() {
   133  		defer f.GetWaitGroup().Done()
   134  		for {
   135  			msg, err := stream.Recv()
   136  			if err != nil {
   137  				if err != io.EOF {
   138  					// Communication error.
   139  					err = pgerror.Newf(pgcode.InternalConnectionFailure, "communication error: %s", err)
   140  					sendErrToConsumer(err)
   141  					errChan <- err
   142  					return
   143  				}
   144  				// End of the stream.
   145  				sendErrToConsumer(nil)
   146  				errChan <- nil
   147  				return
   148  			}
   149  
   150  			if res := processProducerMessage(
   151  				ctx, stream, dst, &sd, &draining, msg,
   152  			); res.err != nil || res.consumerClosed {
   153  				sendErrToConsumer(res.err)
   154  				errChan <- res.err
   155  				return
   156  			}
   157  		}
   158  	}()
   159  
   160  	// Check for context cancellation while reading from the stream on another
   161  	// goroutine.
   162  	select {
   163  	case <-f.GetCtxDone():
   164  		return sqlbase.QueryCanceledError
   165  	case err := <-errChan:
   166  		return err
   167  	}
   168  }
   169  
   170  // sendDrainSignalToProducer is called when the consumer wants to signal the
   171  // producer that it doesn't need any more rows and the producer should drain. A
   172  // signal is sent on stream to the producer to ask it to send metadata.
   173  func sendDrainSignalToStreamProducer(
   174  	ctx context.Context, stream execinfrapb.DistSQL_FlowStreamServer,
   175  ) error {
   176  	log.VEvent(ctx, 1, "sending drain signal to producer")
   177  	sig := execinfrapb.ConsumerSignal{DrainRequest: &execinfrapb.DrainRequest{}}
   178  	return stream.Send(&sig)
   179  }
   180  
   181  // processProducerMessage is a helper function to process data from the producer
   182  // and send it along to the consumer. It keeps track of whether or not it's
   183  // draining between calls. If err in the result is set (or if the consumer is
   184  // closed), the caller must return the error to the producer.
   185  func processProducerMessage(
   186  	ctx context.Context,
   187  	stream execinfrapb.DistSQL_FlowStreamServer,
   188  	dst execinfra.RowReceiver,
   189  	sd *StreamDecoder,
   190  	draining *bool,
   191  	msg *execinfrapb.ProducerMessage,
   192  ) processMessageResult {
   193  	err := sd.AddMessage(ctx, msg)
   194  	if err != nil {
   195  		return processMessageResult{
   196  			err: errors.Wrapf(err, "%s",
   197  				log.MakeMessage(ctx, "decoding error", nil /* args */)),
   198  			consumerClosed: false,
   199  		}
   200  	}
   201  	var types []*types.T
   202  	for {
   203  		row, meta, err := sd.GetRow(nil /* rowBuf */)
   204  		if err != nil {
   205  			return processMessageResult{err: err, consumerClosed: false}
   206  		}
   207  		if row == nil && meta == nil {
   208  			// No more rows in the last message.
   209  			return processMessageResult{err: nil, consumerClosed: false}
   210  		}
   211  
   212  		if log.V(3) && row != nil {
   213  			if types == nil {
   214  				types = sd.Types()
   215  			}
   216  			log.Infof(ctx, "inbound stream pushing row %s", row.String(types))
   217  		}
   218  		if *draining && meta == nil {
   219  			// Don't forward data rows when we're draining.
   220  			continue
   221  		}
   222  		switch dst.Push(row, meta) {
   223  		case execinfra.NeedMoreRows:
   224  			continue
   225  		case execinfra.DrainRequested:
   226  			// The rest of rows are not needed by the consumer. We'll send a drain
   227  			// signal to the producer and expect it to quickly send trailing
   228  			// metadata and close its side of the stream, at which point we also
   229  			// close the consuming side of the stream and call dst.ProducerDone().
   230  			if !*draining {
   231  				*draining = true
   232  				if err := sendDrainSignalToStreamProducer(ctx, stream); err != nil {
   233  					log.Errorf(ctx, "draining error: %s", err)
   234  				}
   235  			}
   236  		case execinfra.ConsumerClosed:
   237  			return processMessageResult{err: nil, consumerClosed: true}
   238  		}
   239  	}
   240  }
   241  
   242  type processMessageResult struct {
   243  	err            error
   244  	consumerClosed bool
   245  }