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  }