Skip to content

Commit 4389315

Browse files
fix(colexec): honor cancellation in window pipelines (#26080)
fix(colexec): honor cancellation in window pipelines Approved by: @XuPeng-SH
1 parent e145324 commit 4389315

5 files changed

Lines changed: 407 additions & 0 deletions

File tree

pkg/sql/colexec/partition/partition.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ import (
3030

3131
const opName = "partition"
3232

33+
const cancellationCheckInterval = 1024
34+
35+
func checkCanceled(proc *process.Process, iteration int) error {
36+
if iteration&(cancellationCheckInterval-1) != 0 {
37+
return nil
38+
}
39+
if err, canceled := vm.CancelCheck(proc); canceled {
40+
return err
41+
}
42+
return nil
43+
}
44+
3345
func (partition *Partition) String(buf *bytes.Buffer) {
3446
buf.WriteString(opName)
3547
buf.WriteString(": partition([")
@@ -191,6 +203,9 @@ func (ctr *container) pickAndSend(proc *process.Process, result *vm.CallResult)
191203
var cols []*vector.Vector
192204
fromRemoveBatch := false
193205
for {
206+
if err = checkCanceled(proc, wholeLength); err != nil {
207+
return false, err
208+
}
194209
if wholeLength == 0 || fromRemoveBatch {
195210
choice = ctr.pickFirstRow()
196211
} else {

pkg/sql/colexec/partition/partition_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package partition
1616

1717
import (
1818
"bytes"
19+
"context"
1920
"testing"
2021

2122
"github.com/matrixorigin/matrixone/pkg/common/mpool"
@@ -82,6 +83,31 @@ func TestPartition(t *testing.T) {
8283
}
8384
}
8485

86+
func TestPartitionOutputHonorsCancellation(t *testing.T) {
87+
proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero())
88+
arg := &Partition{
89+
OrderBySpecs: []*plan.OrderBySpec{{Expr: newExpression(0, types.T_int32)}},
90+
}
91+
require.NoError(t, arg.Prepare(proc))
92+
93+
bat := colexec.MakeMockPartitionBatchs(1, proc.Mp())
94+
arg.ctr.batchList = append(arg.ctr.batchList, bat)
95+
require.NoError(t, arg.ctr.evaluateOrderColumn(proc, 0))
96+
arg.ctr.indexList = []int64{0}
97+
arg.ctr.status = eval
98+
99+
ctx, cancel := context.WithCancel(proc.Ctx)
100+
proc.Ctx = ctx
101+
cancel()
102+
103+
_, err := arg.Call(proc)
104+
require.ErrorIs(t, err, context.Canceled)
105+
106+
arg.Free(proc, true, err)
107+
proc.Free()
108+
require.Equal(t, int64(0), proc.Mp().CurrNB())
109+
}
110+
85111
func newExpression(pos int32, typeID types.T) *plan.Expr {
86112
return &plan.Expr{
87113
Expr: &plan.Expr_Col{

pkg/sql/colexec/window/value_window_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package window
1616

1717
import (
18+
"context"
1819
"math"
1920
"testing"
2021

@@ -213,6 +214,43 @@ func TestProcessValueFunc_NthValue(t *testing.T) {
213214
}
214215
}
215216

217+
func TestProcessValueFuncHonorsCancellation(t *testing.T) {
218+
testCases := []struct {
219+
name string
220+
spec func() *plan.Expr
221+
}{
222+
{name: "lag", spec: makeLagWindowSpec},
223+
{name: "lead", spec: makeLeadWindowSpec},
224+
{name: "first_value", spec: makeFirstValueWindowSpec},
225+
{name: "last_value", spec: makeLastValueWindowSpec},
226+
{name: "nth_value", spec: makeNthValueWindowSpec},
227+
}
228+
229+
for _, tc := range testCases {
230+
t.Run(tc.name, func(t *testing.T) {
231+
mp := mpool.MustNewZero()
232+
proc := testutil.NewProcessWithMPool(t, "", mp)
233+
bat := makeInt32Batch(mp, []int32{10, 20})
234+
ctr := &container{bat: bat}
235+
ctr.aggVecs = make([]colexec.ExprEvalVector, 1)
236+
ctr.aggVecs[0].Vec = []*vector.Vector{bat.Vecs[0]}
237+
arg := &Window{WinSpecList: []*plan.Expr{tc.spec()}}
238+
239+
ctx, cancel := context.WithCancel(proc.Ctx)
240+
proc.Ctx = ctx
241+
cancel()
242+
243+
result, err := ctr.processValueFunc(0, arg, proc)
244+
require.ErrorIs(t, err, context.Canceled)
245+
require.Nil(t, result)
246+
247+
bat.Clean(mp)
248+
proc.Free()
249+
require.Equal(t, int64(0), mp.CurrNB())
250+
})
251+
}
252+
}
253+
216254
func TestProcessValueFunc_ErrorPathFreesLocalResult(t *testing.T) {
217255
mp := mpool.MustNewZero()
218256
resultMP, err := mpool.NewMPool("value-window-error", 1<<20, mpool.NoFixed)

pkg/sql/colexec/window/window.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@ import (
3737

3838
const opName = "window"
3939

40+
// A Window call can spend a long time inside one frame evaluation, especially
41+
// for running frames whose aggregate work is quadratic in the partition size.
42+
// Keep the cancellation polling overhead bounded while still allowing KILL
43+
// QUERY / KILL CONNECTION to interrupt that work promptly.
44+
const cancellationCheckInterval = 1024
45+
46+
func checkCanceled(proc *process.Process, iteration int) error {
47+
if iteration&(cancellationCheckInterval-1) != 0 {
48+
return nil
49+
}
50+
if err, canceled := vm.CancelCheck(proc); canceled {
51+
return err
52+
}
53+
return nil
54+
}
55+
4056
func (window *Window) String(buf *bytes.Buffer) {
4157
buf.WriteString(opName)
4258
buf.WriteString(": window")
@@ -80,6 +96,9 @@ func (window *Window) Call(proc *process.Process) (vm.CallResult, error) {
8096
ctr := &window.ctr
8197

8298
for {
99+
if err, canceled := vm.CancelCheck(proc); canceled {
100+
return vm.CancelResult, err
101+
}
83102
switch ctr.status {
84103
case receiveAll:
85104
for {
@@ -154,6 +173,9 @@ func (window *Window) Call(proc *process.Process) (vm.CallResult, error) {
154173
}
155174
// calculate
156175
for i, w := range window.WinSpecList {
176+
if err = checkCanceled(proc, 0); err != nil {
177+
return result, err
178+
}
157179
// sort and partitions
158180
if window.Fs = makeOrderBy(w); window.Fs != nil {
159181
if len(ctr.orderVecs) == 0 {
@@ -277,7 +299,13 @@ func (ctr *container) processFunc(idx int, ap *Window, proc *process.Process, an
277299

278300
o := 0
279301
for p := 1; p < len(ctr.ps); p++ {
302+
if err = checkCanceled(proc, p); err != nil {
303+
return err
304+
}
280305
for ; o < len(ctr.os); o++ {
306+
if err = checkCanceled(proc, o); err != nil {
307+
return err
308+
}
281309

282310
if ctr.os[o] <= ctr.ps[p] {
283311
// For NTILE, pass both os vector and bucket count vector
@@ -302,6 +330,9 @@ func (ctr *container) processFunc(idx int, ap *Window, proc *process.Process, an
302330
} else {
303331
// plan.Function_AGG
304332
for j := 0; j < n; j++ {
333+
if err = checkCanceled(proc, j); err != nil {
334+
return err
335+
}
305336

306337
start, end := 0, n
307338

@@ -326,6 +357,9 @@ func (ctr *container) processFunc(idx int, ap *Window, proc *process.Process, an
326357
}
327358

328359
for k := left; k < right; k++ {
360+
if err = checkCanceled(proc, k-left); err != nil {
361+
return err
362+
}
329363
if err = ctr.batAggs[idx].Fill(j, k, ctr.aggVecs[idx].Vec); err != nil {
330364
return err
331365
}
@@ -390,6 +424,9 @@ func (ctr *container) processValueFunc(idx int, ap *Window, proc *process.Proces
390424
defaultVec = ctr.aggVecs[idx].Vec[2]
391425
}
392426
for j := 0; j < n; j++ {
427+
if err = checkCanceled(proc, j); err != nil {
428+
return nil, err
429+
}
393430
offset, ok := constOffset, constOK
394431
if offsetVec != nil && !offsetVec.IsConst() {
395432
offset, ok = getInt64FromVec(offsetVec, j)
@@ -430,6 +467,9 @@ func (ctr *container) processValueFunc(idx int, ap *Window, proc *process.Proces
430467
defaultVec = ctr.aggVecs[idx].Vec[2]
431468
}
432469
for j := 0; j < n; j++ {
470+
if err = checkCanceled(proc, j); err != nil {
471+
return nil, err
472+
}
433473
offset, ok := constOffset, constOK
434474
if offsetVec != nil && !offsetVec.IsConst() {
435475
offset, ok = getInt64FromVec(offsetVec, j)
@@ -458,6 +498,9 @@ func (ctr *container) processValueFunc(idx int, ap *Window, proc *process.Proces
458498

459499
case "first_value":
460500
for j := 0; j < n; j++ {
501+
if err = checkCanceled(proc, j); err != nil {
502+
return nil, err
503+
}
461504
start, end := 0, n
462505
if ctr.ps != nil {
463506
start, end = buildPartitionInterval(ctr.ps, j, n)
@@ -485,6 +528,9 @@ func (ctr *container) processValueFunc(idx int, ap *Window, proc *process.Proces
485528

486529
case "last_value":
487530
for j := 0; j < n; j++ {
531+
if err = checkCanceled(proc, j); err != nil {
532+
return nil, err
533+
}
488534
start, end := 0, n
489535
if ctr.ps != nil {
490536
start, end = buildPartitionInterval(ctr.ps, j, n)
@@ -521,6 +567,9 @@ func (ctr *container) processValueFunc(idx int, ap *Window, proc *process.Proces
521567
}
522568
}
523569
for j := 0; j < n; j++ {
570+
if err = checkCanceled(proc, j); err != nil {
571+
return nil, err
572+
}
524573
nthVal, ok := constNth, constOK
525574
if nthVec != nil && !nthVec.IsConst() {
526575
nthVal, ok = getInt64FromVec(nthVec, j)
@@ -834,15 +883,24 @@ func (ctr *container) processOrder(idx int, ap *Window, bat *batch.Batch, proc *
834883
// }
835884
ctr.sels = make([]int64, rowCount)
836885
for i := 0; i < rowCount; i++ {
886+
if err := checkCanceled(proc, i); err != nil {
887+
return false, err
888+
}
837889
ctr.sels[i] = int64(i)
838890
}
839891

840892
// skip sort for const vector
841893
if !ovec.IsConst() {
894+
if err := checkCanceled(proc, 0); err != nil {
895+
return false, err
896+
}
842897
nullCnt := ovec.GetNulls().Count()
843898
if nullCnt < ovec.Length() {
844899
sort.Sort(ctr.desc[0], ctr.nullsLast[0], nullCnt > 0, ctr.sels, ovec)
845900
}
901+
if err := checkCanceled(proc, 0); err != nil {
902+
return false, err
903+
}
846904
}
847905

848906
ps := make([]int64, 0, 16)
@@ -853,6 +911,9 @@ func (ctr *container) processOrder(idx int, ap *Window, bat *batch.Batch, proc *
853911

854912
i, j := 1, len(ctr.orderVecs)
855913
for ; i < j; i++ {
914+
if err := checkCanceled(proc, 0); err != nil {
915+
return false, err
916+
}
856917
desc := ctr.desc[i]
857918
nullsLast := ctr.nullsLast[i]
858919
ps = partition.Partition(ctr.sels, ds, ps, ovec)
@@ -862,6 +923,9 @@ func (ctr *container) processOrder(idx int, ap *Window, bat *batch.Batch, proc *
862923
nullCnt := vec.GetNulls().Count()
863924
if nullCnt < vec.Length() {
864925
for i, j := 0, len(ps); i < j; i++ {
926+
if err := checkCanceled(proc, i); err != nil {
927+
return false, err
928+
}
865929
if i == j-1 {
866930
sort.Sort(desc, nullsLast, nullCnt > 0, ctr.sels[ps[i]:], vec)
867931
} else {
@@ -870,6 +934,9 @@ func (ctr *container) processOrder(idx int, ap *Window, bat *batch.Batch, proc *
870934
}
871935
}
872936
}
937+
if err := checkCanceled(proc, 0); err != nil {
938+
return false, err
939+
}
873940
ovec = vec
874941
if n == i {
875942
ctr.ps = make([]int64, len(ps))

0 commit comments

Comments
 (0)