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 }