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 }