171 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    Copyright The containerd Authors.
 | |
| 
 | |
|    Licensed under the Apache License, Version 2.0 (the "License");
 | |
|    you may not use this file except in compliance with the License.
 | |
|    You may obtain a copy of the License at
 | |
| 
 | |
|        http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
|    Unless required by applicable law or agreed to in writing, software
 | |
|    distributed under the License is distributed on an "AS IS" BASIS,
 | |
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|    See the License for the specific language governing permissions and
 | |
|    limitations under the License.
 | |
| */
 | |
| 
 | |
| package kmutex
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/containerd/containerd/v2/internal/randutil"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| )
 | |
| 
 | |
| func TestBasic(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	km := newKeyMutex()
 | |
| 	ctx := context.Background()
 | |
| 
 | |
| 	km.Lock(ctx, "c1")
 | |
| 	km.Lock(ctx, "c2")
 | |
| 
 | |
| 	assert.Equal(t, len(km.locks), 2)
 | |
| 	assert.Equal(t, km.locks["c1"].ref, 1)
 | |
| 	assert.Equal(t, km.locks["c2"].ref, 1)
 | |
| 
 | |
| 	checkWaitFn := func(key string, num int) {
 | |
| 		retries := 100
 | |
| 		waitLock := false
 | |
| 
 | |
| 		for i := 0; i < retries; i++ {
 | |
| 			// prevent from data-race
 | |
| 			km.mu.Lock()
 | |
| 			ref := km.locks[key].ref
 | |
| 			km.mu.Unlock()
 | |
| 
 | |
| 			if ref == num {
 | |
| 				waitLock = true
 | |
| 				break
 | |
| 			}
 | |
| 			time.Sleep(time.Duration(randutil.Int63n(100)) * time.Millisecond)
 | |
| 		}
 | |
| 		assert.Equal(t, waitLock, true)
 | |
| 	}
 | |
| 
 | |
| 	// should acquire successfully after release
 | |
| 	{
 | |
| 		waitCh := make(chan struct{})
 | |
| 		go func() {
 | |
| 			defer close(waitCh)
 | |
| 
 | |
| 			km.Lock(ctx, "c1")
 | |
| 		}()
 | |
| 		checkWaitFn("c1", 2)
 | |
| 
 | |
| 		km.Unlock("c1")
 | |
| 
 | |
| 		<-waitCh
 | |
| 		assert.Equal(t, km.locks["c1"].ref, 1)
 | |
| 	}
 | |
| 
 | |
| 	// failed to acquire if context cancel
 | |
| 	{
 | |
| 		var errCh = make(chan error, 1)
 | |
| 
 | |
| 		ctx, cancel := context.WithCancel(context.Background())
 | |
| 		go func() {
 | |
| 			errCh <- km.Lock(ctx, "c1")
 | |
| 		}()
 | |
| 
 | |
| 		checkWaitFn("c1", 2)
 | |
| 
 | |
| 		cancel()
 | |
| 		assert.Equal(t, <-errCh, context.Canceled)
 | |
| 		assert.Equal(t, km.locks["c1"].ref, 1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReleasePanic(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	km := newKeyMutex()
 | |
| 
 | |
| 	defer func() {
 | |
| 		if recover() == nil {
 | |
| 			t.Fatal("release of unlocked key did not panic")
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	km.Unlock(t.Name())
 | |
| }
 | |
| 
 | |
| func TestMultileAcquireOnKeys(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	km := newKeyMutex()
 | |
| 	nloops := 10000
 | |
| 	nproc := runtime.GOMAXPROCS(0)
 | |
| 	ctx := context.Background()
 | |
| 
 | |
| 	var wg sync.WaitGroup
 | |
| 	for i := 0; i < nproc; i++ {
 | |
| 		wg.Add(1)
 | |
| 
 | |
| 		go func(key string) {
 | |
| 			defer wg.Done()
 | |
| 
 | |
| 			for i := 0; i < nloops; i++ {
 | |
| 				km.Lock(ctx, key)
 | |
| 
 | |
| 				time.Sleep(time.Duration(randutil.Int63n(100)) * time.Nanosecond)
 | |
| 
 | |
| 				km.Unlock(key)
 | |
| 			}
 | |
| 		}("key-" + strconv.Itoa(i))
 | |
| 	}
 | |
| 	wg.Wait()
 | |
| }
 | |
| 
 | |
| func TestMultiAcquireOnSameKey(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	km := newKeyMutex()
 | |
| 	key := "c1"
 | |
| 	ctx := context.Background()
 | |
| 
 | |
| 	assert.Nil(t, km.Lock(ctx, key))
 | |
| 
 | |
| 	nproc := runtime.GOMAXPROCS(0)
 | |
| 	nloops := 10000
 | |
| 
 | |
| 	var wg sync.WaitGroup
 | |
| 	for i := 0; i < nproc; i++ {
 | |
| 		wg.Add(1)
 | |
| 
 | |
| 		go func() {
 | |
| 			defer wg.Done()
 | |
| 
 | |
| 			for i := 0; i < nloops; i++ {
 | |
| 				km.Lock(ctx, key)
 | |
| 
 | |
| 				time.Sleep(time.Duration(randutil.Int63n(100)) * time.Nanosecond)
 | |
| 
 | |
| 				km.Unlock(key)
 | |
| 			}
 | |
| 		}()
 | |
| 	}
 | |
| 	km.Unlock(key)
 | |
| 	wg.Wait()
 | |
| 
 | |
| 	// c1 key has been released so the it should not have any klock.
 | |
| 	assert.Equal(t, len(km.locks), 0)
 | |
| }
 | 
