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

     1  // Copyright 2019 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  	"sync"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/kv"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    21  	"github.com/cockroachdb/cockroach/pkg/util/contextutil"
    22  	"github.com/cockroachdb/cockroach/pkg/util/log"
    23  	"github.com/cockroachdb/errors"
    24  	"github.com/opentracing/opentracing-go"
    25  )
    26  
    27  type flowStatus int
    28  
    29  // Flow status indicators.
    30  const (
    31  	FlowNotStarted flowStatus = iota
    32  	FlowRunning
    33  	FlowFinished
    34  )
    35  
    36  // Startable is any component that can be started (a router or an outbox).
    37  type Startable interface {
    38  	Start(ctx context.Context, wg *sync.WaitGroup, ctxCancel context.CancelFunc)
    39  }
    40  
    41  // StartableFn is an adapter when a customer function (i.e. a custom goroutine)
    42  // needs to become Startable.
    43  type StartableFn func(context.Context, *sync.WaitGroup, context.CancelFunc)
    44  
    45  // Start is a part of the Startable interface.
    46  func (f StartableFn) Start(ctx context.Context, wg *sync.WaitGroup, ctxCancel context.CancelFunc) {
    47  	f(ctx, wg, ctxCancel)
    48  }
    49  
    50  // FuseOpt specifies options for processor fusing at Flow.Setup() time.
    51  type FuseOpt bool
    52  
    53  const (
    54  	// FuseNormally means fuse what you can, but don't serialize unordered input
    55  	// synchronizers.
    56  	FuseNormally FuseOpt = false
    57  	// FuseAggressively means serialize unordered input synchronizers.
    58  	// This is useful for flows that might have mutations which can't have any
    59  	// concurrency.
    60  	FuseAggressively = true
    61  )
    62  
    63  // Flow represents a flow which consists of processors and streams.
    64  type Flow interface {
    65  	// Setup sets up all the infrastructure for the flow as defined by the flow
    66  	// spec. The flow will then need to be started and run. A new context (along
    67  	// with a context cancellation function) is derived. The new context must be
    68  	// used when running a flow so that all components running in their own
    69  	// goroutines could listen for a cancellation on the same context.
    70  	Setup(ctx context.Context, spec *execinfrapb.FlowSpec, opt FuseOpt) (context.Context, error)
    71  
    72  	// SetTxn is used to provide the transaction in which the flow will run.
    73  	// It needs to be called after Setup() and before Start/Run.
    74  	SetTxn(*kv.Txn)
    75  
    76  	// Start starts the flow. Processors run asynchronously in their own goroutines.
    77  	// Wait() needs to be called to wait for the flow to finish.
    78  	// See Run() for a synchronous version.
    79  	//
    80  	// Generally if errors are encountered during the setup part, they're returned.
    81  	// But if the flow is a synchronous one, then no error is returned; instead the
    82  	// setup error is pushed to the syncFlowConsumer. In this case, a subsequent
    83  	// call to f.Wait() will not block.
    84  	Start(_ context.Context, doneFn func()) error
    85  
    86  	// Run runs the flow to completion. The last processor is run in the current
    87  	// goroutine; others may run in different goroutines depending on how the flow
    88  	// was configured.
    89  	// f.Wait() is called internally, so the call blocks until all the flow's
    90  	// goroutines are done.
    91  	// The caller needs to call f.Cleanup().
    92  	Run(_ context.Context, doneFn func()) error
    93  
    94  	// Wait waits for all the goroutines for this flow to exit. If the context gets
    95  	// canceled before all goroutines exit, it calls f.cancel().
    96  	Wait()
    97  
    98  	// IsLocal returns whether this flow does not have any remote execution.
    99  	IsLocal() bool
   100  
   101  	// IsVectorized returns whether this flow will run with vectorized execution.
   102  	IsVectorized() bool
   103  
   104  	// GetFlowCtx returns the flow context of this flow.
   105  	GetFlowCtx() *execinfra.FlowCtx
   106  
   107  	// AddStartable accumulates a Startable object.
   108  	AddStartable(Startable)
   109  
   110  	// GetID returns the flow ID.
   111  	GetID() execinfrapb.FlowID
   112  
   113  	// Cleanup should be called when the flow completes (after all processors and
   114  	// mailboxes exited).
   115  	Cleanup(context.Context)
   116  
   117  	// ConcurrentExecution returns true if multiple processors/operators in the
   118  	// flow will execute concurrently (i.e. if not all of them have been fused).
   119  	// Can only be called after Setup().
   120  	ConcurrentExecution() bool
   121  }
   122  
   123  // FlowBase is the shared logic between row based and vectorized flows. It
   124  // implements Flow interface for convenience and for usage in tests, but if
   125  // FlowBase.Setup is called, it'll panic.
   126  type FlowBase struct {
   127  	execinfra.FlowCtx
   128  
   129  	flowRegistry *FlowRegistry
   130  	// processors contains a subset of the processors in the flow - the ones that
   131  	// run in their own goroutines. Some processors that implement RowSource are
   132  	// scheduled to run in their consumer's goroutine; those are not present here.
   133  	processors []execinfra.Processor
   134  	// startables are entities that must be started when the flow starts;
   135  	// currently these are outboxes and routers.
   136  	startables []Startable
   137  	// syncFlowConsumer is a special outbox which instead of sending rows to
   138  	// another host, returns them directly (as a result to a SetupSyncFlow RPC,
   139  	// or to the local host).
   140  	syncFlowConsumer execinfra.RowReceiver
   141  
   142  	localProcessors []execinfra.LocalProcessor
   143  
   144  	// startedGoroutines specifies whether this flow started any goroutines. This
   145  	// is used in Wait() to avoid the overhead of waiting for non-existent
   146  	// goroutines.
   147  	startedGoroutines bool
   148  
   149  	// inboundStreams are streams that receive data from other hosts; this map
   150  	// is to be passed to FlowRegistry.RegisterFlow.
   151  	inboundStreams map[execinfrapb.StreamID]*InboundStreamInfo
   152  
   153  	// waitGroup is used to wait for async components of the flow:
   154  	//  - processors
   155  	//  - inbound streams
   156  	//  - outboxes
   157  	waitGroup sync.WaitGroup
   158  
   159  	doneFn func()
   160  
   161  	status flowStatus
   162  
   163  	// Cancel function for ctx. Call this to cancel the flow (safe to be called
   164  	// multiple times).
   165  	ctxCancel context.CancelFunc
   166  	ctxDone   <-chan struct{}
   167  
   168  	// spec is the request that produced this flow. Only used for debugging.
   169  	spec *execinfrapb.FlowSpec
   170  }
   171  
   172  // Setup is part of the Flow interface.
   173  func (f *FlowBase) Setup(
   174  	ctx context.Context, spec *execinfrapb.FlowSpec, _ FuseOpt,
   175  ) (context.Context, error) {
   176  	ctx, f.ctxCancel = contextutil.WithCancel(ctx)
   177  	f.ctxDone = ctx.Done()
   178  	f.spec = spec
   179  	return ctx, nil
   180  }
   181  
   182  // SetTxn is part of the Flow interface.
   183  func (f *FlowBase) SetTxn(txn *kv.Txn) {
   184  	f.FlowCtx.Txn = txn
   185  	f.EvalCtx.Txn = txn
   186  }
   187  
   188  // ConcurrentExecution is part of the Flow interface.
   189  func (f *FlowBase) ConcurrentExecution() bool {
   190  	return len(f.processors) > 1
   191  }
   192  
   193  var _ Flow = &FlowBase{}
   194  
   195  // NewFlowBase creates a new FlowBase.
   196  func NewFlowBase(
   197  	flowCtx execinfra.FlowCtx,
   198  	flowReg *FlowRegistry,
   199  	syncFlowConsumer execinfra.RowReceiver,
   200  	localProcessors []execinfra.LocalProcessor,
   201  ) *FlowBase {
   202  	base := &FlowBase{
   203  		FlowCtx:          flowCtx,
   204  		flowRegistry:     flowReg,
   205  		syncFlowConsumer: syncFlowConsumer,
   206  		localProcessors:  localProcessors,
   207  	}
   208  	base.status = FlowNotStarted
   209  	return base
   210  }
   211  
   212  // GetFlowCtx is part of the Flow interface.
   213  func (f *FlowBase) GetFlowCtx() *execinfra.FlowCtx {
   214  	return &f.FlowCtx
   215  }
   216  
   217  // AddStartable is part of the Flow interface.
   218  func (f *FlowBase) AddStartable(s Startable) {
   219  	f.startables = append(f.startables, s)
   220  }
   221  
   222  // GetID is part of the Flow interface.
   223  func (f *FlowBase) GetID() execinfrapb.FlowID {
   224  	return f.ID
   225  }
   226  
   227  // CheckInboundStreamID takes a stream ID and returns an error if an inbound
   228  // stream already exists with that ID in the inbound streams map, creating the
   229  // inbound streams map if it is nil.
   230  func (f *FlowBase) CheckInboundStreamID(sid execinfrapb.StreamID) error {
   231  	if _, found := f.inboundStreams[sid]; found {
   232  		return errors.Errorf("inbound stream %d already exists in map", sid)
   233  	}
   234  	if f.inboundStreams == nil {
   235  		f.inboundStreams = make(map[execinfrapb.StreamID]*InboundStreamInfo)
   236  	}
   237  	return nil
   238  }
   239  
   240  // GetWaitGroup returns the wait group of this flow.
   241  func (f *FlowBase) GetWaitGroup() *sync.WaitGroup {
   242  	return &f.waitGroup
   243  }
   244  
   245  // GetCtxDone returns done channel of the context of this flow.
   246  func (f *FlowBase) GetCtxDone() <-chan struct{} {
   247  	return f.ctxDone
   248  }
   249  
   250  // GetCancelFlowFn returns the context cancellation function of the context of
   251  // this flow.
   252  func (f *FlowBase) GetCancelFlowFn() context.CancelFunc {
   253  	return f.ctxCancel
   254  }
   255  
   256  // SetProcessors overrides the current f.processors with the provided
   257  // processors. This is used to set up the vectorized flow.
   258  func (f *FlowBase) SetProcessors(processors []execinfra.Processor) {
   259  	f.processors = processors
   260  }
   261  
   262  // AddRemoteStream adds a remote stream to this flow.
   263  func (f *FlowBase) AddRemoteStream(streamID execinfrapb.StreamID, streamInfo *InboundStreamInfo) {
   264  	f.inboundStreams[streamID] = streamInfo
   265  }
   266  
   267  // GetSyncFlowConsumer returns the special syncFlowConsumer outbox.
   268  func (f *FlowBase) GetSyncFlowConsumer() execinfra.RowReceiver {
   269  	return f.syncFlowConsumer
   270  }
   271  
   272  // GetLocalProcessors return the execinfra.LocalProcessors of this flow.
   273  func (f *FlowBase) GetLocalProcessors() []execinfra.LocalProcessor {
   274  	return f.localProcessors
   275  }
   276  
   277  // startInternal starts the flow. All processors are started, each in their own
   278  // goroutine. The caller must forward any returned error to syncFlowConsumer if
   279  // set.
   280  func (f *FlowBase) startInternal(
   281  	ctx context.Context, processors []execinfra.Processor, doneFn func(),
   282  ) error {
   283  	f.doneFn = doneFn
   284  	log.VEventf(
   285  		ctx, 1, "starting (%d processors, %d startables)", len(processors), len(f.startables),
   286  	)
   287  
   288  	// Only register the flow if there will be inbound stream connections that
   289  	// need to look up this flow in the flow registry.
   290  	if !f.IsLocal() {
   291  		// Once we call RegisterFlow, the inbound streams become accessible; we must
   292  		// set up the WaitGroup counter before.
   293  		// The counter will be further incremented below to account for the
   294  		// processors.
   295  		f.waitGroup.Add(len(f.inboundStreams))
   296  
   297  		if err := f.flowRegistry.RegisterFlow(
   298  			ctx, f.ID, f, f.inboundStreams, SettingFlowStreamTimeout.Get(&f.FlowCtx.Cfg.Settings.SV),
   299  		); err != nil {
   300  			return err
   301  		}
   302  	}
   303  
   304  	f.status = FlowRunning
   305  
   306  	if log.V(1) {
   307  		log.Infof(ctx, "registered flow %s", f.ID.Short())
   308  	}
   309  	for _, s := range f.startables {
   310  		s.Start(ctx, &f.waitGroup, f.ctxCancel)
   311  	}
   312  	for i := 0; i < len(processors); i++ {
   313  		f.waitGroup.Add(1)
   314  		go func(i int) {
   315  			processors[i].Run(ctx)
   316  			f.waitGroup.Done()
   317  		}(i)
   318  	}
   319  	f.startedGoroutines = len(f.startables) > 0 || len(processors) > 0 || !f.IsLocal()
   320  	return nil
   321  }
   322  
   323  // IsLocal returns whether this flow does not have any remote execution.
   324  func (f *FlowBase) IsLocal() bool {
   325  	return len(f.inboundStreams) == 0
   326  }
   327  
   328  // IsVectorized returns whether this flow will run with vectorized execution.
   329  func (f *FlowBase) IsVectorized() bool {
   330  	panic("IsVectorized should not be called on FlowBase")
   331  }
   332  
   333  // Start is part of the Flow interface.
   334  func (f *FlowBase) Start(ctx context.Context, doneFn func()) error {
   335  	if err := f.startInternal(ctx, f.processors, doneFn); err != nil {
   336  		// For sync flows, the error goes to the consumer.
   337  		if f.syncFlowConsumer != nil {
   338  			f.syncFlowConsumer.Push(nil /* row */, &execinfrapb.ProducerMetadata{Err: err})
   339  			f.syncFlowConsumer.ProducerDone()
   340  			return nil
   341  		}
   342  		return err
   343  	}
   344  	return nil
   345  }
   346  
   347  // Run is part of the Flow interface.
   348  func (f *FlowBase) Run(ctx context.Context, doneFn func()) error {
   349  	defer f.Wait()
   350  
   351  	// We'll take care of the last processor in particular.
   352  	var headProc execinfra.Processor
   353  	if len(f.processors) == 0 {
   354  		return errors.AssertionFailedf("no processors in flow")
   355  	}
   356  	headProc = f.processors[len(f.processors)-1]
   357  	otherProcs := f.processors[:len(f.processors)-1]
   358  
   359  	var err error
   360  	if err = f.startInternal(ctx, otherProcs, doneFn); err != nil {
   361  		// For sync flows, the error goes to the consumer.
   362  		if f.syncFlowConsumer != nil {
   363  			f.syncFlowConsumer.Push(nil /* row */, &execinfrapb.ProducerMetadata{Err: err})
   364  			f.syncFlowConsumer.ProducerDone()
   365  			return nil
   366  		}
   367  		return err
   368  	}
   369  	headProc.Run(ctx)
   370  	return nil
   371  }
   372  
   373  // Wait is part of the Flow interface.
   374  func (f *FlowBase) Wait() {
   375  	if !f.startedGoroutines {
   376  		return
   377  	}
   378  
   379  	var panicVal interface{}
   380  	if panicVal = recover(); panicVal != nil {
   381  		// If Wait is called as part of stack unwinding during a panic, the flow
   382  		// context must be canceled to ensure that all asynchronous goroutines get
   383  		// the message that they must exit (otherwise we will wait indefinitely).
   384  		f.ctxCancel()
   385  	}
   386  	waitChan := make(chan struct{})
   387  
   388  	go func() {
   389  		f.waitGroup.Wait()
   390  		close(waitChan)
   391  	}()
   392  
   393  	select {
   394  	case <-f.ctxDone:
   395  		f.cancel()
   396  		<-waitChan
   397  	case <-waitChan:
   398  		// Exit normally
   399  	}
   400  	if panicVal != nil {
   401  		panic(panicVal)
   402  	}
   403  }
   404  
   405  // Releasable is an interface for objects than can be Released back into a
   406  // memory pool when finished.
   407  type Releasable interface {
   408  	// Release allows this object to be returned to a memory pool. Objects must
   409  	// not be used after Release is called.
   410  	Release()
   411  }
   412  
   413  // Cleanup is part of the Flow interface.
   414  // NOTE: this implements only the shared clean up logic between row-based and
   415  // vectorized flows.
   416  func (f *FlowBase) Cleanup(ctx context.Context) {
   417  	if f.status == FlowFinished {
   418  		panic("flow cleanup called twice")
   419  	}
   420  
   421  	// This closes the monitor opened in ServerImpl.setupFlow.
   422  	f.EvalCtx.Stop(ctx)
   423  	for _, p := range f.processors {
   424  		if d, ok := p.(Releasable); ok {
   425  			d.Release()
   426  		}
   427  	}
   428  	if log.V(1) {
   429  		log.Infof(ctx, "cleaning up")
   430  	}
   431  	sp := opentracing.SpanFromContext(ctx)
   432  	// Local flows do not get registered.
   433  	if !f.IsLocal() && f.status != FlowNotStarted {
   434  		f.flowRegistry.UnregisterFlow(f.ID)
   435  	}
   436  	f.status = FlowFinished
   437  	f.ctxCancel()
   438  	if f.doneFn != nil {
   439  		f.doneFn()
   440  	}
   441  	if sp != nil {
   442  		sp.Finish()
   443  	}
   444  }
   445  
   446  // cancel iterates through all unconnected streams of this flow and marks them canceled.
   447  // This function is called in Wait() after the associated context has been canceled.
   448  // In order to cancel a flow, call f.ctxCancel() instead of this function.
   449  //
   450  // For a detailed description of the distsql query cancellation mechanism,
   451  // read docs/RFCS/query_cancellation.md.
   452  func (f *FlowBase) cancel() {
   453  	// If the flow is local, there are no inbound streams to cancel.
   454  	if f.IsLocal() {
   455  		return
   456  	}
   457  	f.flowRegistry.Lock()
   458  	timedOutReceivers := f.flowRegistry.cancelPendingStreamsLocked(f.ID)
   459  	f.flowRegistry.Unlock()
   460  
   461  	for _, receiver := range timedOutReceivers {
   462  		go func(receiver InboundStreamHandler) {
   463  			// Stream has yet to be started; send an error to its
   464  			// receiver and prevent it from being connected.
   465  			receiver.Timeout(sqlbase.QueryCanceledError)
   466  		}(receiver)
   467  	}
   468  }