Skip to content

Commit c3a433b

Browse files
authored
Merge pull request #149920 from yuzefovich/blathers/backport-release-25.3-149800
release-25.3: distsql: fix recently introduced leak of CancelFunc
2 parents 937d66e + 1841771 commit c3a433b

File tree

2 files changed

+33
-13
lines changed

2 files changed

+33
-13
lines changed

pkg/kv/kvclient/kvstreamer/streamer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,9 @@ func (s *Streamer) Enqueue(ctx context.Context, reqs []kvpb.RequestUnion) (retEr
532532
},
533533
s.coordinator.mainLoop,
534534
); err != nil {
535+
// The server is shutting down. It's ok to not call
536+
// s.coordinatorCtxCancel in this case.
537+
//
535538
// The new goroutine wasn't spun up, so mainLoop won't get executed
536539
// and we have to decrement the wait group ourselves.
537540
s.waitGroup.Done()

pkg/sql/distsql/server.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,22 @@ func (ds *ServerImpl) setDraining(drain bool) error {
166166
return nil
167167
}
168168

169+
type onFlowCleanupFn func()
170+
171+
func (f onFlowCleanupFn) Do() {
172+
if f != nil {
173+
f()
174+
}
175+
}
176+
169177
// setupFlow creates a Flow.
170-
//
171-
// - reserved: specifies the upfront memory reservation that the flow takes
172-
// ownership of. This account is already closed if an error is returned or
173-
// will be closed through Flow.Cleanup.
174-
//
175-
// - localState: specifies if the flow runs entirely on this node and, if it
176-
// does, specifies the txn and other attributes.
178+
// - reserved: specifies the upfront memory reservation that the flow takes
179+
// ownership of. This account is already closed if an error is returned or will
180+
// be closed through Flow.Cleanup.
181+
// - localState: specifies if the flow runs entirely on this node and, if it
182+
// does, specifies the txn and other attributes.
183+
// - onFlowCleanup, if non-nil, will be called at the end of Flow.Cleanup. It'll
184+
// also be called if this method returns an error.
177185
//
178186
// Note: unless an error is returned, the returned context contains a span that
179187
// must be finished through Flow.Cleanup.
@@ -186,6 +194,7 @@ func (ds *ServerImpl) setupFlow(
186194
rowSyncFlowConsumer execinfra.RowReceiver,
187195
batchSyncFlowConsumer execinfra.BatchReceiver,
188196
localState LocalState,
197+
onFlowCleanup onFlowCleanupFn,
189198
) (retCtx context.Context, _ flowinfra.Flow, _ execopnode.OpChains, retErr error) {
190199
var sp *tracing.Span // will be Finish()ed by Flow.Cleanup()
191200
var monitor, diskMonitor *mon.BytesMonitor // will be closed in Flow.Cleanup()
@@ -204,6 +213,7 @@ func (ds *ServerImpl) setupFlow(
204213
onFlowCleanupEnd(ctx)
205214
} else {
206215
reserved.Close(ctx)
216+
onFlowCleanup.Do()
207217
}
208218
// We finish the span after performing other cleanup in case that
209219
// cleanup accesses the context with the span.
@@ -300,18 +310,21 @@ func (ds *ServerImpl) setupFlow(
300310
onFlowCleanupEnd = func(ctx context.Context) {
301311
localEvalCtx.Txn = origTxn
302312
reserved.Close(ctx)
313+
onFlowCleanup.Do()
303314
}
304315
// Update the Txn field early (before f.SetTxn() below) since some
305316
// processors capture the field in their constructor (see #41992).
306317
localEvalCtx.Txn = leafTxn
307318
} else {
308319
onFlowCleanupEnd = func(ctx context.Context) {
309320
reserved.Close(ctx)
321+
onFlowCleanup.Do()
310322
}
311323
}
312324
} else {
313325
onFlowCleanupEnd = func(ctx context.Context) {
314326
reserved.Close(ctx)
327+
onFlowCleanup.Do()
315328
}
316329
if localState.IsLocal {
317330
return nil, nil, nil, errors.AssertionFailedf(
@@ -579,7 +592,7 @@ func (ds *ServerImpl) SetupLocalSyncFlow(
579592
) (context.Context, flowinfra.Flow, execopnode.OpChains, error) {
580593
return ds.setupFlow(
581594
ctx, tracing.SpanFromContext(ctx), parentMonitor, &mon.BoundAccount{}, /* reserved */
582-
req, output, batchOutput, localState,
595+
req, output, batchOutput, localState, nil, /* onFlowCleanup */
583596
)
584597
}
585598

@@ -639,10 +652,6 @@ func (ds *ServerImpl) SetupFlow(
639652
// Note: the passed context will be canceled when this RPC completes, so we
640653
// can't associate it with the flow since it outlives the RPC.
641654
ctx = ds.AnnotateCtx(context.Background())
642-
// Ensure that the flow respects the node being shut down. Note that since
643-
// the flow outlives the RPC, we cannot defer the cancel function, so we
644-
// simply ignore it.
645-
ctx, _ = ds.Stopper.WithCancelOnQuiesce(ctx)
646655
if err := func() error {
647656
// Reserve some memory for this remote flow which is a poor man's
648657
// admission control based on the RAM usage.
@@ -651,10 +660,18 @@ func (ds *ServerImpl) SetupFlow(
651660
if err != nil {
652661
return err
653662
}
663+
// Ensure that the flow respects the node being shut down. We can only
664+
// call the cancellation function once the flow exits.
665+
//
666+
// setupFlow will either call 'cancel' if an error is returned, or the
667+
// cancellation function is taken over by the flow, and it'll be called
668+
// in Flow.Cleanup.
669+
var cancel context.CancelFunc
670+
ctx, cancel = ds.Stopper.WithCancelOnQuiesce(ctx)
654671
var f flowinfra.Flow
655672
ctx, f, _, err = ds.setupFlow(
656673
ctx, rpcSpan, ds.memMonitor, &reserved, req, nil, /* rowSyncFlowConsumer */
657-
nil /* batchSyncFlowConsumer */, LocalState{},
674+
nil /* batchSyncFlowConsumer */, LocalState{}, onFlowCleanupFn(cancel),
658675
)
659676
// Check whether the RPC context has been canceled indicating that we
660677
// actually don't need to run this flow. This can happen when the

0 commit comments

Comments
 (0)