2020-03-14 12:11:46 +00:00
|
|
|
package keylock_test
|
|
|
|
|
|
|
|
import (
|
2024-06-05 17:36:34 +00:00
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"slices"
|
2020-03-14 12:11:46 +00:00
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2024-06-05 17:36:34 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2020-03-14 12:11:46 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/goleak"
|
|
|
|
|
2024-06-05 17:36:34 +00:00
|
|
|
"gitlab.com/slon/shad-go/keylock"
|
|
|
|
"gitlab.com/slon/shad-go/tools/testtool"
|
2020-03-14 12:11:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func timeout(d time.Duration) <-chan struct{} {
|
|
|
|
ch := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
time.Sleep(d)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
return ch
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestKeyLock_Simple(t *testing.T) {
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
|
|
|
|
l := keylock.New()
|
|
|
|
|
2023-03-27 19:30:25 +00:00
|
|
|
canceled, unlock0 := l.LockKeys([]string{"a", "b"}, nil)
|
2020-03-16 10:20:09 +00:00
|
|
|
require.False(t, canceled)
|
2020-03-14 12:11:46 +00:00
|
|
|
|
2020-03-16 10:20:09 +00:00
|
|
|
canceled, _ = l.LockKeys([]string{"", "b", "c"}, timeout(time.Millisecond*10))
|
|
|
|
require.True(t, canceled)
|
2020-03-14 12:11:46 +00:00
|
|
|
|
2023-03-27 19:30:25 +00:00
|
|
|
unlock0()
|
2020-03-14 12:11:46 +00:00
|
|
|
|
2023-03-27 19:30:25 +00:00
|
|
|
canceled, unlock1 := l.LockKeys([]string{"", "b", "c"}, nil)
|
2020-03-16 10:20:09 +00:00
|
|
|
require.False(t, canceled)
|
2023-03-27 19:30:25 +00:00
|
|
|
unlock1()
|
2020-03-14 12:11:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestKeyLock_Progress(t *testing.T) {
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
l := keylock.New()
|
|
|
|
|
2023-03-27 19:30:25 +00:00
|
|
|
canceled, unlock0 := l.LockKeys([]string{"a", "b"}, nil)
|
2020-03-16 10:20:09 +00:00
|
|
|
require.False(t, canceled)
|
2023-03-27 19:30:25 +00:00
|
|
|
defer unlock0()
|
2020-03-14 12:11:46 +00:00
|
|
|
|
|
|
|
go func() {
|
2023-03-27 19:30:25 +00:00
|
|
|
_, unlock := l.LockKeys([]string{"b", "c"}, nil)
|
|
|
|
unlock()
|
2020-03-14 12:11:46 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
time.Sleep(time.Millisecond * 10)
|
2023-03-27 19:30:25 +00:00
|
|
|
canceled, unlock1 := l.LockKeys([]string{"d"}, nil)
|
2020-03-16 10:20:09 +00:00
|
|
|
require.False(t, canceled)
|
2023-03-27 19:30:25 +00:00
|
|
|
unlock1()
|
2020-03-14 12:11:46 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 17:36:34 +00:00
|
|
|
func TestKeyLock_NoBusyWait(t *testing.T) {
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
l := keylock.New()
|
|
|
|
|
|
|
|
_, unlock0 := l.LockKeys([]string{"a", "b"}, nil)
|
|
|
|
defer unlock0()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
_, unlock := l.LockKeys([]string{"b", "c"}, nil)
|
|
|
|
unlock()
|
|
|
|
}()
|
|
|
|
|
|
|
|
testtool.VerifyNoBusyGoroutines(t)
|
|
|
|
}
|
|
|
|
|
2020-03-14 12:11:46 +00:00
|
|
|
func TestKeyLock_DeadlockFree(t *testing.T) {
|
|
|
|
const N = 10000
|
|
|
|
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
l := keylock.New()
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(3)
|
|
|
|
|
|
|
|
checkLock := func(keys []string) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
for i := 0; i < N; i++ {
|
2020-03-14 21:53:10 +00:00
|
|
|
cancelled, unlock := l.LockKeys(keys, nil)
|
|
|
|
if cancelled {
|
2020-03-14 12:11:46 +00:00
|
|
|
t.Error("spurious lock failure")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go checkLock([]string{"a", "b", "c"})
|
|
|
|
go checkLock([]string{"b", "c", "a"})
|
|
|
|
go checkLock([]string{"c", "a", "b"})
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
2021-02-21 22:35:45 +00:00
|
|
|
func TestKeyLock_NoMutates(t *testing.T) {
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
l := keylock.New()
|
|
|
|
|
|
|
|
keys := []string{"b", "c", "a"}
|
|
|
|
passedKeys := make([]string, len(keys))
|
|
|
|
copy(passedKeys, keys)
|
|
|
|
|
|
|
|
_, unlock := l.LockKeys(passedKeys, nil)
|
|
|
|
unlock()
|
|
|
|
|
|
|
|
require.Equal(t, keys, passedKeys, "passed keys shouldn't be mutated")
|
|
|
|
}
|
|
|
|
|
2020-03-14 12:11:46 +00:00
|
|
|
func TestKeyLock_SingleKeyStress(t *testing.T) {
|
|
|
|
const (
|
|
|
|
N = 1000
|
|
|
|
G = 100
|
|
|
|
)
|
|
|
|
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
l := keylock.New()
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(G)
|
|
|
|
|
|
|
|
for i := 0; i < G; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
2024-06-05 17:36:34 +00:00
|
|
|
for j := 0; j < N; j++ {
|
2020-03-14 21:53:10 +00:00
|
|
|
cancelled, unlock := l.LockKeys([]string{"a"}, timeout(time.Millisecond))
|
|
|
|
if !cancelled {
|
2020-03-14 12:11:46 +00:00
|
|
|
unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
2024-06-05 17:36:34 +00:00
|
|
|
|
|
|
|
func TestKeyLock_MutualExclusionStress(t *testing.T) {
|
|
|
|
const (
|
|
|
|
N = 1000
|
|
|
|
G = 100
|
|
|
|
M = 15
|
|
|
|
K = 3
|
|
|
|
)
|
|
|
|
|
|
|
|
defer goleak.VerifyNone(t)
|
|
|
|
|
|
|
|
locked := map[string]bool{}
|
|
|
|
var mu sync.Mutex
|
|
|
|
|
|
|
|
l := keylock.New()
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(G)
|
|
|
|
|
|
|
|
for i := 0; i < G; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
for j := 0; j < N; j++ {
|
|
|
|
keys := []string{}
|
|
|
|
for k := 0; k < K; k++ {
|
|
|
|
keys = append(keys, fmt.Sprint(rand.Intn(N)))
|
|
|
|
}
|
|
|
|
|
|
|
|
slices.Sort(keys)
|
|
|
|
keys = slices.Compact(keys)
|
|
|
|
|
|
|
|
_, unlock := l.LockKeys(keys, nil)
|
|
|
|
mu.Lock()
|
|
|
|
for _, key := range keys {
|
|
|
|
assert.False(t, locked[key])
|
|
|
|
locked[key] = true
|
|
|
|
}
|
|
|
|
mu.Unlock()
|
|
|
|
|
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
for _, key := range keys {
|
|
|
|
locked[key] = false
|
|
|
|
}
|
|
|
|
mu.Unlock()
|
|
|
|
|
|
|
|
unlock()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|