| /* |
| 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) |
| } |