1package locker
2
3import (
4	"math/rand"
5	"strconv"
6	"sync"
7	"testing"
8	"time"
9)
10
11func TestLockCounter(t *testing.T) {
12	l := &lockCtr{}
13	l.inc()
14
15	if l.waiters != 1 {
16		t.Fatal("counter inc failed")
17	}
18
19	l.dec()
20	if l.waiters != 0 {
21		t.Fatal("counter dec failed")
22	}
23}
24
25func TestLockerLock(t *testing.T) {
26	l := New()
27	l.Lock("test")
28	ctr := l.locks["test"]
29
30	if ctr.count() != 0 {
31		t.Fatalf("expected waiters to be 0, got :%d", ctr.waiters)
32	}
33
34	chDone := make(chan struct{})
35	go func() {
36		l.Lock("test")
37		close(chDone)
38	}()
39
40	chWaiting := make(chan struct{})
41	go func() {
42		for range time.Tick(1 * time.Millisecond) {
43			if ctr.count() == 1 {
44				close(chWaiting)
45				break
46			}
47		}
48	}()
49
50	select {
51	case <-chWaiting:
52	case <-time.After(3 * time.Second):
53		t.Fatal("timed out waiting for lock waiters to be incremented")
54	}
55
56	select {
57	case <-chDone:
58		t.Fatal("lock should not have returned while it was still held")
59	default:
60	}
61
62	if err := l.Unlock("test"); err != nil {
63		t.Fatal(err)
64	}
65
66	select {
67	case <-chDone:
68	case <-time.After(3 * time.Second):
69		t.Fatalf("lock should have completed")
70	}
71
72	if ctr.count() != 0 {
73		t.Fatalf("expected waiters to be 0, got: %d", ctr.count())
74	}
75}
76
77func TestLockerUnlock(t *testing.T) {
78	l := New()
79
80	l.Lock("test")
81	l.Unlock("test")
82
83	chDone := make(chan struct{})
84	go func() {
85		l.Lock("test")
86		close(chDone)
87	}()
88
89	select {
90	case <-chDone:
91	case <-time.After(3 * time.Second):
92		t.Fatalf("lock should not be blocked")
93	}
94}
95
96func TestLockerConcurrency(t *testing.T) {
97	l := New()
98
99	var wg sync.WaitGroup
100	for i := 0; i <= 10000; i++ {
101		wg.Add(1)
102		go func() {
103			l.Lock("test")
104			// if there is a concurrency issue, will very likely panic here
105			l.Unlock("test")
106			wg.Done()
107		}()
108	}
109
110	chDone := make(chan struct{})
111	go func() {
112		wg.Wait()
113		close(chDone)
114	}()
115
116	select {
117	case <-chDone:
118	case <-time.After(10 * time.Second):
119		t.Fatal("timeout waiting for locks to complete")
120	}
121
122	// Since everything has unlocked this should not exist anymore
123	if ctr, exists := l.locks["test"]; exists {
124		t.Fatalf("lock should not exist: %v", ctr)
125	}
126}
127
128func BenchmarkLocker(b *testing.B) {
129	l := New()
130	for i := 0; i < b.N; i++ {
131		l.Lock("test")
132		l.Unlock("test")
133	}
134}
135
136func BenchmarkLockerParallel(b *testing.B) {
137	l := New()
138	b.SetParallelism(128)
139	b.RunParallel(func(pb *testing.PB) {
140		for pb.Next() {
141			l.Lock("test")
142			l.Unlock("test")
143		}
144	})
145}
146
147func BenchmarkLockerMoreKeys(b *testing.B) {
148	l := New()
149	var keys []string
150	for i := 0; i < 64; i++ {
151		keys = append(keys, strconv.Itoa(i))
152	}
153	b.SetParallelism(128)
154	b.RunParallel(func(pb *testing.PB) {
155		for pb.Next() {
156			k := keys[rand.Intn(len(keys))]
157			l.Lock(k)
158			l.Unlock(k)
159		}
160	})
161}
162