Merge branch 'keylock' into 'master'

Add keylock task

See merge request slon/shad-go-private!17
This commit is contained in:
Fedor Korotkiy 2020-03-14 12:32:18 +00:00
commit 35df338792
4 changed files with 205 additions and 0 deletions

17
keylock/README.md Normal file
View file

@ -0,0 +1,17 @@
# keylock
Напишите примитив синхронизации, позволяющий "лочить" строки из множества.
```go
package keylock
type KeyLock interface {
// LockKeys locks all keys from provided set.
//
// Upon successful completion, function guarantees that no other call with intersecting set of keys
// will finish, until unlock() is called.
//
// If cancel channel is closed, function returns immediately.
LockKeys(keys []string, cancel <-chan struct{}) (canceled bool, unlock func())
}
```

13
keylock/keylock.go Normal file
View file

@ -0,0 +1,13 @@
// +build !solution
package keylock
type KeyLock struct{}
func New() *KeyLock {
panic("implement me")
}
func (l *KeyLock) LockKeys(keys []string, cancel <-chan struct{}) (canceled bool, unlock func()) {
panic("implement me")
}

112
keylock/keylock_test.go Normal file
View file

@ -0,0 +1,112 @@
package keylock_test
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"gitlab.com/slon/shad-go/keylock"
)
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()
locked, unlock := l.LockKeys([]string{"a", "b"}, nil)
require.True(t, locked)
locked, _ = l.LockKeys([]string{"", "b", "c"}, timeout(time.Millisecond*10))
require.False(t, locked)
unlock()
locked, _ = l.LockKeys([]string{"", "b", "c"}, nil)
require.True(t, locked)
}
func TestKeyLock_Progress(t *testing.T) {
defer goleak.VerifyNone(t)
l := keylock.New()
locked, unlock := l.LockKeys([]string{"a", "b"}, nil)
require.True(t, locked)
defer unlock()
go func() {
_, _ = l.LockKeys([]string{"b", "c"}, nil)
}()
time.Sleep(time.Millisecond * 10)
locked, _ = l.LockKeys([]string{"d"}, nil)
require.True(t, locked)
}
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++ {
locked, unlock := l.LockKeys(keys, nil)
if !locked {
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()
}
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()
for i := 0; i < N; i++ {
locked, unlock := l.LockKeys([]string{"a"}, timeout(time.Millisecond))
if locked {
unlock()
}
}
}()
}
wg.Wait()
}

63
keylock/speed_test.go Normal file
View file

@ -0,0 +1,63 @@
package keylock
import (
"math/rand"
"strconv"
"sync"
"testing"
)
func BenchmarkMutex_Baseline(b *testing.B) {
var mu sync.Mutex
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
_ = 0
mu.Unlock()
}
})
}
func BenchmarkKeyLock_SingleKey(b *testing.B) {
l := New()
keys := []string{"a"}
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, unlock := l.LockKeys(keys, nil)
unlock()
}
})
}
func BenchmarkKeyLock_MultipleKeys(b *testing.B) {
l := New()
keys := []string{"a", "b", "c", "d"}
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, unlock := l.LockKeys(keys, nil)
unlock()
}
})
}
func BenchmarkKeyLock_DifferentKeys(b *testing.B) {
l := New()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
keys := []string{strconv.Itoa(rand.Int())}
for pb.Next() {
_, unlock := l.LockKeys(keys, nil)
unlock()
}
})
}