github.com/matrixorigin/matrixone@v0.7.0/pkg/lockservice/service_test.go (about)

     1  // Copyright 2022 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 lockservice
    16  
    17  import (
    18  	"context"
    19  	"encoding/binary"
    20  	"strconv"
    21  	"sync"
    22  	"sync/atomic"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/fagongzi/goetty/v2/buf"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func TestRowLock(t *testing.T) {
    32  	l := NewLockService()
    33  	ctx := context.Background()
    34  	option := LockOptions{
    35  		granularity: Row,
    36  		mode:        Exclusive,
    37  		policy:      Wait,
    38  	}
    39  	acquired := false
    40  
    41  	err := l.Lock(context.Background(), 0, [][]byte{{1}}, []byte{1}, option)
    42  	assert.NoError(t, err)
    43  	go func() {
    44  		err := l.Lock(ctx, 0, [][]byte{{1}}, []byte{2}, option)
    45  		assert.NoError(t, err)
    46  		acquired = true
    47  		err = l.Unlock([]byte{2})
    48  		assert.NoError(t, err)
    49  	}()
    50  	time.Sleep(time.Second / 2)
    51  	err = l.Unlock([]byte{1})
    52  	assert.NoError(t, err)
    53  	time.Sleep(time.Second / 2)
    54  	err = l.Lock(context.Background(), 0, [][]byte{{1}}, []byte{3}, option)
    55  	assert.NoError(t, err)
    56  	assert.True(t, acquired)
    57  
    58  	err = l.Unlock([]byte{3})
    59  	assert.NoError(t, err)
    60  }
    61  
    62  func TestMultipleRowLocks(t *testing.T) {
    63  	l := NewLockService()
    64  	ctx := context.Background()
    65  	option := LockOptions{
    66  		granularity: Row,
    67  		mode:        Exclusive,
    68  		policy:      Wait,
    69  	}
    70  	iter := 0
    71  	sum := 10000
    72  	var wg sync.WaitGroup
    73  
    74  	for i := 0; i < sum; i++ {
    75  		wg.Add(1)
    76  		go func(i int) {
    77  			err := l.Lock(ctx, 0, [][]byte{{1}}, []byte(strconv.Itoa(i)), option)
    78  			assert.NoError(t, err)
    79  			iter++
    80  			err = l.Unlock([]byte(strconv.Itoa(i)))
    81  			assert.NoError(t, err)
    82  			wg.Done()
    83  		}(i)
    84  	}
    85  	wg.Wait()
    86  	assert.Equal(t, sum, iter)
    87  }
    88  
    89  func TestCtxCancelWhileWaiting(t *testing.T) {
    90  	l := NewLockService()
    91  	ctx, cancel := context.WithCancel(context.Background())
    92  	option := LockOptions{Row, Exclusive, Wait}
    93  	var wg sync.WaitGroup
    94  	wg.Add(1)
    95  	err := l.Lock(ctx, 0, [][]byte{{1}}, []byte("txn1"), option)
    96  	assert.NoError(t, err)
    97  	go func(ctx context.Context) {
    98  		err := l.Lock(ctx, 0, [][]byte{{1}}, []byte("txn2"), option)
    99  		assert.Error(t, err)
   100  		wg.Done()
   101  	}(ctx)
   102  	cancel()
   103  	wg.Wait()
   104  	assert.NoError(t, l.Unlock([]byte(strconv.Itoa(1))))
   105  }
   106  
   107  func TestDeadLock(t *testing.T) {
   108  	l := NewLockService().(*service)
   109  	ctx, cancel := context.WithCancel(context.Background())
   110  	defer cancel()
   111  
   112  	txn1 := []byte("txn1")
   113  	txn2 := []byte("txn2")
   114  	txn3 := []byte("txn3")
   115  	row1 := []byte{1}
   116  	row2 := []byte{2}
   117  	row3 := []byte{3}
   118  
   119  	mustAddTestLock(t, ctx, l, txn1, [][]byte{row1}, Row)
   120  	mustAddTestLock(t, ctx, l, txn2, [][]byte{row2}, Row)
   121  	mustAddTestLock(t, ctx, l, txn3, [][]byte{row3}, Row)
   122  
   123  	var wg sync.WaitGroup
   124  	wg.Add(3)
   125  	maxDeadLockCount := uint32(1)
   126  	deadLockCounter := atomic.Uint32{}
   127  	go func() {
   128  		defer wg.Done()
   129  		maybeAddTestLockWithDeadlock(t, ctx, l, txn1, [][]byte{row2}, Row, &deadLockCounter, maxDeadLockCount)
   130  		require.NoError(t, l.Unlock(txn1))
   131  	}()
   132  	go func() {
   133  		defer wg.Done()
   134  		maybeAddTestLockWithDeadlock(t, ctx, l, txn2, [][]byte{row3}, Row, &deadLockCounter, maxDeadLockCount)
   135  		require.NoError(t, l.Unlock(txn2))
   136  	}()
   137  	go func() {
   138  		defer wg.Done()
   139  		maybeAddTestLockWithDeadlock(t, ctx, l, txn3, [][]byte{row1}, Row, &deadLockCounter, maxDeadLockCount)
   140  		require.NoError(t, l.Unlock(txn3))
   141  	}()
   142  	wg.Wait()
   143  }
   144  
   145  func TestDeadLockWithRange(t *testing.T) {
   146  	l := NewLockService().(*service)
   147  	ctx, cancel := context.WithCancel(context.Background())
   148  	defer cancel()
   149  
   150  	txn1 := []byte("txn1")
   151  	txn2 := []byte("txn2")
   152  	txn3 := []byte("txn3")
   153  	row1 := []byte{1, 2}
   154  	row2 := []byte{3, 4}
   155  	row3 := []byte{5, 6}
   156  
   157  	mustAddTestLock(t, ctx, l, txn1, [][]byte{row1}, Range)
   158  	mustAddTestLock(t, ctx, l, txn2, [][]byte{row2}, Range)
   159  	mustAddTestLock(t, ctx, l, txn3, [][]byte{row3}, Range)
   160  
   161  	var wg sync.WaitGroup
   162  	wg.Add(3)
   163  	maxDeadLockCount := uint32(1)
   164  	var deadLockCounter atomic.Uint32
   165  	go func() {
   166  		defer wg.Done()
   167  		maybeAddTestLockWithDeadlock(t, ctx, l, txn1, [][]byte{row2}, Range, &deadLockCounter, maxDeadLockCount)
   168  		require.NoError(t, l.Unlock(txn1))
   169  	}()
   170  	go func() {
   171  		defer wg.Done()
   172  		maybeAddTestLockWithDeadlock(t, ctx, l, txn2, [][]byte{row3}, Range, &deadLockCounter, maxDeadLockCount)
   173  		require.NoError(t, l.Unlock(txn2))
   174  	}()
   175  	go func() {
   176  		defer wg.Done()
   177  		maybeAddTestLockWithDeadlock(t, ctx, l, txn3, [][]byte{row1}, Range, &deadLockCounter, maxDeadLockCount)
   178  		require.NoError(t, l.Unlock(txn3))
   179  	}()
   180  	wg.Wait()
   181  }
   182  
   183  func mustAddTestLock(t *testing.T,
   184  	ctx context.Context,
   185  	l *service,
   186  	txnID []byte,
   187  	lock [][]byte,
   188  	granularity Granularity) {
   189  	maybeAddTestLockWithDeadlock(t,
   190  		ctx,
   191  		l,
   192  		txnID,
   193  		lock,
   194  		granularity,
   195  		nil,
   196  		0)
   197  }
   198  
   199  func maybeAddTestLockWithDeadlock(t *testing.T,
   200  	ctx context.Context,
   201  	l *service,
   202  	txnID []byte,
   203  	lock [][]byte,
   204  	granularity Granularity,
   205  	deadLockCount *atomic.Uint32,
   206  	maxDeadLockCount uint32) {
   207  	t.Logf("%s try lock %+v", string(txnID), lock)
   208  	err := l.Lock(ctx, 1, lock, txnID, LockOptions{
   209  		granularity: Row,
   210  		mode:        Exclusive,
   211  		policy:      Wait,
   212  	})
   213  	if err == ErrDeadlockDetectorClosed {
   214  		t.Logf("%s lock %+v, found dead lock", string(txnID), lock)
   215  		require.True(t, maxDeadLockCount >= deadLockCount.Add(1))
   216  		return
   217  	}
   218  	t.Logf("%s lock %+v, ok", string(txnID), lock)
   219  	require.NoError(t, err)
   220  }
   221  
   222  func TestRangeLock(t *testing.T) {
   223  	l := NewLockService()
   224  	ctx := context.Background()
   225  	option := LockOptions{
   226  		granularity: Range,
   227  		mode:        Exclusive,
   228  		policy:      Wait,
   229  	}
   230  	acquired := false
   231  
   232  	err := l.Lock(context.Background(), 0, [][]byte{{1}, {2}}, []byte{1}, option)
   233  	assert.NoError(t, err)
   234  	go func() {
   235  		err := l.Lock(ctx, 0, [][]byte{{1}, {2}}, []byte{2}, option)
   236  		assert.NoError(t, err)
   237  		acquired = true
   238  		err = l.Unlock([]byte{2})
   239  		assert.NoError(t, err)
   240  	}()
   241  	time.Sleep(time.Second / 2)
   242  	err = l.Unlock([]byte{1})
   243  	assert.NoError(t, err)
   244  	time.Sleep(time.Second / 2)
   245  	err = l.Lock(context.Background(), 0, [][]byte{{1}, {2}}, []byte{3}, option)
   246  	assert.NoError(t, err)
   247  	assert.True(t, acquired)
   248  
   249  	err = l.Unlock([]byte{3})
   250  	assert.NoError(t, err)
   251  }
   252  
   253  func TestMultipleRangeLocks(t *testing.T) {
   254  	l := NewLockService()
   255  	ctx := context.Background()
   256  	option := LockOptions{
   257  		granularity: Range,
   258  		mode:        Exclusive,
   259  		policy:      Wait,
   260  	}
   261  
   262  	sum := 100
   263  	var wg sync.WaitGroup
   264  	for i := 0; i < sum; i++ {
   265  		wg.Add(1)
   266  		go func(i int) {
   267  			defer wg.Done()
   268  
   269  			start := i % 10
   270  			if start == 9 {
   271  				return
   272  			}
   273  			end := (i + 1) % 10
   274  			err := l.Lock(ctx, 0, [][]byte{{byte(start)}, {byte(end)}}, []byte(strconv.Itoa(i)), option)
   275  			assert.NoError(t, err)
   276  			err = l.Unlock([]byte(strconv.Itoa(i)))
   277  			assert.NoError(t, err)
   278  		}(i)
   279  	}
   280  	wg.Wait()
   281  }
   282  
   283  func BenchmarkMultipleRowLock(b *testing.B) {
   284  	b.Run("lock-service", func(b *testing.B) {
   285  		l := NewLockService()
   286  		ctx := context.Background()
   287  		option := LockOptions{
   288  			granularity: Row,
   289  			mode:        Exclusive,
   290  			policy:      Wait,
   291  		}
   292  		iter := 0
   293  
   294  		for i := 0; i < b.N; i++ {
   295  			go func(i int) {
   296  				bs := make([]byte, 4)
   297  				binary.LittleEndian.PutUint32(bs, uint32(i))
   298  				err := l.Lock(ctx, 0, [][]byte{{1}}, bs, option)
   299  				assert.NoError(b, err)
   300  				iter++
   301  				err = l.Unlock(bs)
   302  				assert.NoError(b, err)
   303  			}(i)
   304  		}
   305  	})
   306  }
   307  
   308  func BenchmarkWithoutConflict(b *testing.B) {
   309  	runBenchmark(b, "1-table", 1)
   310  	runBenchmark(b, "unlimited-table", 32)
   311  }
   312  
   313  var tableID atomic.Uint64
   314  var txnID atomic.Uint64
   315  var rowID atomic.Uint64
   316  
   317  func runBenchmark(b *testing.B, name string, t uint64) {
   318  	b.Run(name, func(b *testing.B) {
   319  		l := NewLockService()
   320  		getTableID := func() uint64 {
   321  			if t == 1 {
   322  				return 0
   323  			}
   324  			return tableID.Add(1)
   325  		}
   326  
   327  		// total p goroutines to run test
   328  		b.ReportAllocs()
   329  		b.ResetTimer()
   330  
   331  		b.RunParallel(func(p *testing.PB) {
   332  			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   333  			defer cancel()
   334  
   335  			row := [][]byte{buf.Uint64ToBytes(rowID.Add(1))}
   336  			txn := buf.Uint64ToBytes(txnID.Add(1))
   337  			table := getTableID()
   338  			// fmt.Printf("on table %d\n", table)
   339  			for p.Next() {
   340  				if err := l.Lock(ctx, table, row, txn, LockOptions{}); err != nil {
   341  					panic(err)
   342  				}
   343  				if err := l.Unlock(txn); err != nil {
   344  					panic(err)
   345  				}
   346  			}
   347  		})
   348  	})
   349  
   350  }