Add keylock task
This commit is contained in:
parent
b2e6e86a8c
commit
51d12b7db7
4 changed files with 204 additions and 0 deletions
17
keylock/README.md
Normal file
17
keylock/README.md
Normal 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
13
keylock/keylock.go
Normal 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
112
keylock/keylock_test.go
Normal 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()
|
||||
}
|
62
keylock/speed_test.go
Normal file
62
keylock/speed_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
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()
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue