058c4dfc3e
Can't safely use uint32 for locker since we need to decrement the count, which requires loading the unit and doing some math, which is inherintly racey. Instead use Int32 which we can safely use with atomic and AddInt32 with `-1` Signed-off-by: Brian Goff <cpuguy83@gmail.com>
124 lines
2 KiB
Go
124 lines
2 KiB
Go
package locker
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLockCounter(t *testing.T) {
|
|
l := &lockCtr{}
|
|
l.inc()
|
|
|
|
if l.waiters != 1 {
|
|
t.Fatal("counter inc failed")
|
|
}
|
|
|
|
l.dec()
|
|
if l.waiters != 0 {
|
|
t.Fatal("counter dec failed")
|
|
}
|
|
}
|
|
|
|
func TestLockerLock(t *testing.T) {
|
|
l := New()
|
|
l.Lock("test")
|
|
ctr := l.locks["test"]
|
|
|
|
if ctr.count() != 0 {
|
|
t.Fatalf("expected waiters to be 0, got :%d", ctr.waiters)
|
|
}
|
|
|
|
chDone := make(chan struct{})
|
|
go func() {
|
|
l.Lock("test")
|
|
close(chDone)
|
|
}()
|
|
|
|
chWaiting := make(chan struct{})
|
|
go func() {
|
|
for range time.Tick(1 * time.Millisecond) {
|
|
if ctr.count() == 1 {
|
|
close(chWaiting)
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-chWaiting:
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("timed out waiting for lock waiters to be incremented")
|
|
}
|
|
|
|
select {
|
|
case <-chDone:
|
|
t.Fatal("lock should not have returned while it was still held")
|
|
default:
|
|
}
|
|
|
|
if err := l.Unlock("test"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case <-chDone:
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatalf("lock should have completed")
|
|
}
|
|
|
|
if ctr.count() != 0 {
|
|
t.Fatalf("expected waiters to be 0, got: %d", ctr.count())
|
|
}
|
|
}
|
|
|
|
func TestLockerUnlock(t *testing.T) {
|
|
l := New()
|
|
|
|
l.Lock("test")
|
|
l.Unlock("test")
|
|
|
|
chDone := make(chan struct{})
|
|
go func() {
|
|
l.Lock("test")
|
|
close(chDone)
|
|
}()
|
|
|
|
select {
|
|
case <-chDone:
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatalf("lock should not be blocked")
|
|
}
|
|
}
|
|
|
|
func TestLockerConcurrency(t *testing.T) {
|
|
l := New()
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i <= 10000; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
l.Lock("test")
|
|
// if there is a concurrency issue, will very likely panic here
|
|
l.Unlock("test")
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
chDone := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
close(chDone)
|
|
}()
|
|
|
|
select {
|
|
case <-chDone:
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("timeout waiting for locks to complete")
|
|
}
|
|
|
|
// Since everything has unlocked this should not exist anymore
|
|
if ctr, exists := l.locks["test"]; exists {
|
|
t.Fatalf("lock should not exist: %v", ctr)
|
|
}
|
|
}
|