github.com/matrixorigin/matrixone@v1.2.0/pkg/sql/colexec/lockop/lock_op_test.go (about) 1 // Copyright 2023 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package lockop 16 17 import ( 18 "context" 19 "math" 20 "testing" 21 "time" 22 23 "github.com/lni/goutils/leaktest" 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 "github.com/matrixorigin/matrixone/pkg/common/mpool" 26 "github.com/matrixorigin/matrixone/pkg/common/runtime" 27 "github.com/matrixorigin/matrixone/pkg/common/stopper" 28 "github.com/matrixorigin/matrixone/pkg/container/batch" 29 "github.com/matrixorigin/matrixone/pkg/container/types" 30 "github.com/matrixorigin/matrixone/pkg/container/vector" 31 "github.com/matrixorigin/matrixone/pkg/lockservice" 32 "github.com/matrixorigin/matrixone/pkg/pb/lock" 33 "github.com/matrixorigin/matrixone/pkg/pb/timestamp" 34 "github.com/matrixorigin/matrixone/pkg/sql/colexec/value_scan" 35 "github.com/matrixorigin/matrixone/pkg/txn/client" 36 "github.com/matrixorigin/matrixone/pkg/txn/rpc" 37 "github.com/matrixorigin/matrixone/pkg/vm" 38 "github.com/matrixorigin/matrixone/pkg/vm/engine" 39 "github.com/matrixorigin/matrixone/pkg/vm/process" 40 "github.com/stretchr/testify/assert" 41 "github.com/stretchr/testify/require" 42 "go.uber.org/zap" 43 ) 44 45 var testFunc = func( 46 proc *process.Process, 47 rel engine.Relation, 48 tableID uint64, 49 eng engine.Engine, 50 vec *vector.Vector, 51 from, to timestamp.Timestamp) (bool, error) { 52 return false, nil 53 } 54 55 func TestCallLockOpWithNoConflict(t *testing.T) { 56 runLockNonBlockingOpTest( 57 t, 58 []uint64{1}, 59 [][]int32{{0, 1, 2}}, 60 func(proc *process.Process, arg *Argument) { 61 require.NoError(t, arg.Prepare(proc)) 62 arg.rt.hasNewVersionInRange = testFunc 63 result, err := arg.Call(proc) 64 require.NoError(t, err) 65 66 vec := result.Batch.GetVector(1) 67 values := vector.MustFixedCol[types.TS](vec) 68 assert.Equal(t, 3, len(values)) 69 for _, v := range values { 70 assert.Equal(t, types.TS{}, v) 71 } 72 }, 73 client.WithEnableRefreshExpression(), 74 ) 75 } 76 77 func TestCallLockOpWithConflict(t *testing.T) { 78 tableID := uint64(10) 79 runLockNonBlockingOpTest( 80 t, 81 []uint64{tableID}, 82 [][]int32{{0, 1, 2}}, 83 func(proc *process.Process, arg *Argument) { 84 require.NoError(t, arg.Prepare(proc)) 85 arg.rt.hasNewVersionInRange = testFunc 86 87 arg.rt.parker.Reset() 88 arg.rt.parker.EncodeInt32(0) 89 conflictRow := arg.rt.parker.Bytes() 90 _, err := proc.LockService.Lock( 91 proc.Ctx, 92 tableID, 93 [][]byte{conflictRow}, 94 []byte("txn01"), 95 lock.LockOptions{}) 96 require.NoError(t, err) 97 98 c := make(chan struct{}) 99 go func() { 100 defer close(c) 101 result, err := arg.Call(proc) 102 require.NoError(t, err) 103 104 vec := result.Batch.GetVector(1) 105 values := vector.MustFixedCol[types.TS](vec) 106 assert.Equal(t, 3, len(values)) 107 for _, v := range values { 108 assert.Equal(t, types.BuildTS(math.MaxInt64, 1), v) 109 } 110 }() 111 require.NoError(t, lockservice.WaitWaiters(proc.LockService, 0, tableID, conflictRow, 1)) 112 require.NoError(t, proc.LockService.Unlock(proc.Ctx, []byte("txn01"), timestamp.Timestamp{PhysicalTime: math.MaxInt64})) 113 <-c 114 }, 115 client.WithEnableRefreshExpression(), 116 ) 117 } 118 119 func TestCallLockOpWithConflictWithRefreshNotEnabled(t *testing.T) { 120 tableID := uint64(10) 121 runLockNonBlockingOpTest( 122 t, 123 []uint64{tableID}, 124 [][]int32{{0, 1, 2}}, 125 func(proc *process.Process, arg *Argument) { 126 require.NoError(t, arg.Prepare(proc)) 127 arg.rt.hasNewVersionInRange = testFunc 128 129 arg.rt.parker.Reset() 130 arg.rt.parker.EncodeInt32(0) 131 conflictRow := arg.rt.parker.Bytes() 132 _, err := proc.LockService.Lock( 133 proc.Ctx, 134 tableID, 135 [][]byte{conflictRow}, 136 []byte("txn01"), 137 lock.LockOptions{}) 138 require.NoError(t, err) 139 140 c := make(chan struct{}) 141 go func() { 142 defer close(c) 143 arg2 := &Argument{ 144 OperatorBase: vm.OperatorBase{ 145 OperatorInfo: vm.OperatorInfo{ 146 Idx: 1, 147 IsFirst: false, 148 IsLast: false, 149 }, 150 }, 151 } 152 arg2.rt = &state{} 153 arg2.rt.retryError = nil 154 arg2.targets = arg.targets 155 arg2.Prepare(proc) 156 arg2.rt.hasNewVersionInRange = testFunc 157 valueScan := arg.GetChildren(0).(*value_scan.Argument) 158 resetChildren(arg2, valueScan.Batchs[0]) 159 defer arg2.rt.parker.FreeMem() 160 161 _, err = arg2.Call(proc) 162 assert.NoError(t, err) 163 164 resetChildren(arg2, nil) 165 _, err = arg2.Call(proc) 166 require.Error(t, err) 167 assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnNeedRetry)) 168 }() 169 require.NoError(t, lockservice.WaitWaiters(proc.LockService, 0, tableID, conflictRow, 1)) 170 require.NoError(t, proc.LockService.Unlock(proc.Ctx, []byte("txn01"), timestamp.Timestamp{PhysicalTime: math.MaxInt64})) 171 <-c 172 }, 173 ) 174 } 175 176 func TestCallLockOpWithHasPrevCommit(t *testing.T) { 177 tableID := uint64(10) 178 runLockNonBlockingOpTest( 179 t, 180 []uint64{tableID}, 181 [][]int32{{0, 1, 2}}, 182 func(proc *process.Process, arg *Argument) { 183 require.NoError(t, arg.Prepare(proc)) 184 arg.rt.hasNewVersionInRange = testFunc 185 186 arg.rt.parker.Reset() 187 arg.rt.parker.EncodeInt32(0) 188 conflictRow := arg.rt.parker.Bytes() 189 190 // txn01 commit 191 _, err := proc.LockService.Lock( 192 proc.Ctx, 193 tableID, 194 [][]byte{conflictRow}, 195 []byte("txn01"), 196 lock.LockOptions{}) 197 require.NoError(t, err) 198 require.NoError(t, proc.LockService.Unlock(proc.Ctx, []byte("txn01"), timestamp.Timestamp{PhysicalTime: math.MaxInt64})) 199 200 // txn02 abort 201 _, err = proc.LockService.Lock( 202 proc.Ctx, 203 tableID, 204 [][]byte{conflictRow}, 205 []byte("txn02"), 206 lock.LockOptions{}) 207 require.NoError(t, err) 208 209 c := make(chan struct{}) 210 go func() { 211 defer close(c) 212 arg2 := &Argument{ 213 OperatorBase: vm.OperatorBase{ 214 OperatorInfo: vm.OperatorInfo{ 215 Idx: 1, 216 IsFirst: false, 217 IsLast: false, 218 }, 219 }, 220 } 221 arg2.rt = &state{} 222 arg2.rt.retryError = nil 223 arg2.targets = arg.targets 224 arg2.Prepare(proc) 225 arg2.rt.hasNewVersionInRange = testFunc 226 valueScan := arg.GetChildren(0).(*value_scan.Argument) 227 resetChildren(arg2, valueScan.Batchs[0]) 228 defer arg2.rt.parker.FreeMem() 229 230 _, err = arg2.Call(proc) 231 assert.NoError(t, err) 232 233 resetChildren(arg2, nil) 234 _, err = arg2.Call(proc) 235 require.Error(t, err) 236 assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnNeedRetry)) 237 }() 238 require.NoError(t, lockservice.WaitWaiters(proc.LockService, 0, tableID, conflictRow, 1)) 239 require.NoError(t, proc.LockService.Unlock(proc.Ctx, []byte("txn02"), timestamp.Timestamp{})) 240 <-c 241 }, 242 ) 243 } 244 245 func TestCallLockOpWithHasPrevCommitLessMe(t *testing.T) { 246 tableID := uint64(10) 247 runLockNonBlockingOpTest( 248 t, 249 []uint64{tableID}, 250 [][]int32{{0, 1, 2}}, 251 func(proc *process.Process, arg *Argument) { 252 require.NoError(t, arg.Prepare(proc)) 253 arg.rt.hasNewVersionInRange = testFunc 254 255 arg.rt.parker.Reset() 256 arg.rt.parker.EncodeInt32(0) 257 conflictRow := arg.rt.parker.Bytes() 258 259 // txn01 commit 260 _, err := proc.LockService.Lock( 261 proc.Ctx, 262 tableID, 263 [][]byte{conflictRow}, 264 []byte("txn01"), 265 lock.LockOptions{}) 266 require.NoError(t, err) 267 require.NoError(t, proc.LockService.Unlock(proc.Ctx, []byte("txn01"), timestamp.Timestamp{PhysicalTime: math.MaxInt64 - 1})) 268 269 // txn02 abort 270 _, err = proc.LockService.Lock( 271 proc.Ctx, 272 tableID, 273 [][]byte{conflictRow}, 274 []byte("txn02"), 275 lock.LockOptions{}) 276 require.NoError(t, err) 277 278 c := make(chan struct{}) 279 go func() { 280 defer close(c) 281 arg2 := &Argument{ 282 OperatorBase: vm.OperatorBase{ 283 OperatorInfo: vm.OperatorInfo{ 284 Idx: 1, 285 IsFirst: false, 286 IsLast: false, 287 }, 288 }, 289 } 290 arg2.rt = &state{} 291 arg2.rt.retryError = nil 292 arg2.targets = arg.targets 293 arg2.Prepare(proc) 294 arg2.rt.hasNewVersionInRange = testFunc 295 valueScan := arg.GetChildren(0).(*value_scan.Argument) 296 resetChildren(arg2, valueScan.Batchs[0]) 297 defer arg2.rt.parker.FreeMem() 298 299 proc.TxnOperator.TxnRef().SnapshotTS = timestamp.Timestamp{PhysicalTime: math.MaxInt64} 300 301 _, err = arg2.Call(proc) 302 assert.NoError(t, err) 303 304 resetChildren(arg2, nil) 305 _, err = arg2.Call(proc) 306 require.NoError(t, err) 307 }() 308 require.NoError(t, lockservice.WaitWaiters(proc.LockService, 0, tableID, conflictRow, 1)) 309 require.NoError(t, proc.LockService.Unlock(proc.Ctx, []byte("txn02"), timestamp.Timestamp{})) 310 <-c 311 }, 312 ) 313 } 314 315 func TestLockWithBlocking(t *testing.T) { 316 values := [][]int32{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} 317 runLockBlockingOpTest( 318 t, 319 1, 320 values, 321 nil, 322 func( 323 proc *process.Process, 324 arg *Argument, 325 idx int, 326 isFirst, isLast bool) (bool, error) { 327 arg.rt.hasNewVersionInRange = testFunc 328 arg.OperatorBase.OperatorInfo = vm.OperatorInfo{ 329 Idx: idx, 330 IsFirst: isFirst, 331 IsLast: isLast, 332 } 333 end, err := arg.Call(proc) 334 require.NoError(t, err) 335 if end.Batch != nil { 336 end.Batch.Clean(proc.GetMPool()) 337 } 338 if end.Status == vm.ExecStop { 339 if arg.rt.parker != nil { 340 arg.rt.parker.FreeMem() 341 } 342 } 343 return end.Status == vm.ExecStop, nil 344 }, 345 func(arg *Argument, proc *process.Process) { 346 arg.Free(proc, false, nil) 347 proc.FreeVectors() 348 }, 349 ) 350 } 351 352 func TestLockWithBlockingWithConflict(t *testing.T) { 353 tableID := uint64(10) 354 values := [][]int32{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} 355 runLockBlockingOpTest( 356 t, 357 tableID, 358 values, 359 func(proc *process.Process) { 360 parker := types.NewPacker(proc.Mp()) 361 defer parker.FreeMem() 362 363 parker.Reset() 364 parker.EncodeInt32(1) 365 conflictRow := parker.Bytes() 366 367 _, err := proc.LockService.Lock( 368 proc.Ctx, 369 tableID, 370 [][]byte{conflictRow}, 371 []byte("txn01"), 372 lock.LockOptions{}) 373 require.NoError(t, err) 374 375 go func() { 376 require.NoError(t, lockservice.WaitWaiters(proc.LockService, 0, tableID, conflictRow, 1)) 377 require.NoError(t, proc.LockService.Unlock( 378 proc.Ctx, 379 []byte("txn01"), 380 timestamp.Timestamp{PhysicalTime: math.MaxInt64})) 381 }() 382 }, 383 func( 384 proc *process.Process, 385 arg *Argument, 386 idx int, 387 isFirst, isLast bool) (bool, error) { 388 arg.rt.hasNewVersionInRange = testFunc 389 arg.OperatorBase.OperatorInfo = vm.OperatorInfo{ 390 Idx: idx, 391 IsFirst: isFirst, 392 IsLast: isLast, 393 } 394 ok, err := arg.Call(proc) 395 return ok.Status == vm.ExecStop, err 396 }, 397 func(arg *Argument, proc *process.Process) { 398 require.True(t, moerr.IsMoErrCode(arg.rt.retryError, moerr.ErrTxnNeedRetry)) 399 for _, bat := range arg.rt.cachedBatches { 400 bat.Clean(proc.Mp()) 401 } 402 arg.Free(proc, false, nil) 403 proc.FreeVectors() 404 }, 405 ) 406 } 407 408 func TestLockWithHasNewVersionInLockedTS(t *testing.T) { 409 tw := client.NewTimestampWaiter() 410 stopper := stopper.NewStopper("") 411 stopper.RunTask(func(ctx context.Context) { 412 for { 413 select { 414 case <-ctx.Done(): 415 return 416 case <-time.After(time.Millisecond * 100): 417 tw.NotifyLatestCommitTS(timestamp.Timestamp{PhysicalTime: time.Now().UTC().UnixNano()}) 418 } 419 } 420 }) 421 runLockNonBlockingOpTest( 422 t, 423 []uint64{1}, 424 [][]int32{{0, 1, 2}}, 425 func(proc *process.Process, arg *Argument) { 426 require.NoError(t, arg.Prepare(proc)) 427 arg.rt.hasNewVersionInRange = func( 428 proc *process.Process, 429 rel engine.Relation, 430 tableID uint64, 431 eng engine.Engine, 432 vec *vector.Vector, 433 from, to timestamp.Timestamp) (bool, error) { 434 return true, nil 435 } 436 437 _, err := arg.Call(proc) 438 require.NoError(t, err) 439 require.Error(t, arg.rt.retryError) 440 require.True(t, moerr.IsMoErrCode(arg.rt.retryError, moerr.ErrTxnNeedRetry)) 441 }, 442 client.WithTimestampWaiter(tw), 443 ) 444 stopper.Stop() 445 } 446 447 func runLockNonBlockingOpTest( 448 t *testing.T, 449 tables []uint64, 450 values [][]int32, 451 fn func(*process.Process, *Argument), 452 opts ...client.TxnClientCreateOption) { 453 runLockOpTest( 454 t, 455 func(proc *process.Process) { 456 bat := batch.NewWithSize(len(tables) * 2) 457 bat.SetRowCount(len(tables) * 2) 458 459 defer func() { 460 bat.Clean(proc.Mp()) 461 }() 462 463 offset := int32(0) 464 pkType := types.New(types.T_int32, 0, 0) 465 tsType := types.New(types.T_TS, 0, 0) 466 arg := NewArgumentByEngine(nil) 467 arg.OperatorBase.OperatorInfo = vm.OperatorInfo{ 468 Idx: 0, 469 IsFirst: false, 470 IsLast: false, 471 } 472 for idx, table := range tables { 473 arg.AddLockTarget(table, offset, pkType, offset+1) 474 475 vec := vector.NewVec(pkType) 476 vector.AppendFixedList(vec, values[idx], nil, proc.Mp()) 477 bat.Vecs[offset] = vec 478 479 vec = vector.NewVec(tsType) 480 bat.Vecs[offset+1] = vec 481 offset += 2 482 } 483 resetChildren(arg, bat) 484 485 fn(proc, arg) 486 arg.Free(proc, false, nil) 487 }, 488 opts...) 489 } 490 491 func runLockBlockingOpTest( 492 t *testing.T, 493 table uint64, 494 values [][]int32, 495 beforeFunc func(proc *process.Process), 496 fn func(proc *process.Process, arg *Argument, idx int, isFirst, isLast bool) (bool, error), 497 checkFunc func(*Argument, *process.Process), 498 opts ...client.TxnClientCreateOption) { 499 runLockOpTest( 500 t, 501 func(proc *process.Process) { 502 if beforeFunc != nil { 503 beforeFunc(proc) 504 } 505 506 pkType := types.New(types.T_int32, 0, 0) 507 tsType := types.New(types.T_TS, 0, 0) 508 arg := NewArgumentByEngine(nil).SetBlock(true).AddLockTarget(table, 0, pkType, 1) 509 510 var batches []*batch.Batch 511 var batches2 []*batch.Batch 512 for _, vs := range values { 513 bat := batch.NewWithSize(2) 514 bat.SetRowCount(2) 515 516 vec := vector.NewVec(pkType) 517 vector.AppendFixedList(vec, vs, nil, proc.Mp()) 518 bat.Vecs[0] = vec 519 520 vec = vector.NewVec(tsType) 521 bat.Vecs[1] = vec 522 523 batches = append(batches, bat) 524 batches2 = append(batches2, bat) 525 } 526 require.NoError(t, arg.Prepare(proc)) 527 arg.rt.batchFetchFunc = func(process.Analyze) (*batch.Batch, bool, error) { 528 if len(batches) == 0 { 529 return nil, true, nil 530 } 531 bat := batches[0] 532 batches = batches[1:] 533 return bat, false, nil 534 } 535 536 var err error 537 var end bool 538 for { 539 end, err = fn(proc, arg, 1, false, false) 540 if err != nil || end { 541 break 542 } 543 } 544 for _, bat := range batches2 { 545 bat.Clean(proc.Mp()) 546 } 547 checkFunc(arg, proc) 548 }, 549 opts...) 550 } 551 552 func runLockOpTest( 553 t *testing.T, 554 fn func(*process.Process), 555 opts ...client.TxnClientCreateOption) { 556 defer leaktest.AfterTest(t)() 557 lockservice.RunLockServicesForTest( 558 zap.DebugLevel, 559 []string{"s1"}, 560 time.Second, 561 func(_ lockservice.LockTableAllocator, services []lockservice.LockService) { 562 runtime.ProcessLevelRuntime().SetGlobalVariables(runtime.LockService, services[0]) 563 564 // TODO: remove 565 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 566 defer cancel() 567 568 s, err := rpc.NewSender(rpc.Config{}, runtime.ProcessLevelRuntime()) 569 require.NoError(t, err) 570 571 opts = append(opts, client.WithLockService(services[0])) 572 c := client.NewTxnClient(s, opts...) 573 c.Resume() 574 defer func() { 575 assert.NoError(t, c.Close()) 576 }() 577 txnOp, err := c.New(ctx, timestamp.Timestamp{}) 578 require.NoError(t, err) 579 580 proc := process.New( 581 ctx, 582 mpool.MustNewZero(), 583 c, 584 txnOp, 585 nil, 586 services[0], 587 nil, 588 nil, 589 nil, 590 nil) 591 require.Equal(t, int64(0), proc.Mp().CurrNB()) 592 defer func() { 593 require.Equal(t, int64(0), proc.Mp().CurrNB()) 594 }() 595 fn(proc) 596 }, 597 nil, 598 ) 599 } 600 601 func resetChildren(arg *Argument, bat *batch.Batch) { 602 arg.SetChildren( 603 []vm.Operator{ 604 &value_scan.Argument{ 605 Batchs: []*batch.Batch{bat}, 606 }, 607 }) 608 }