Browse code

Fix race in locker call to `dec()`

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>

Brian Goff authored on 2015/11/25 01:52:18
Showing 2 changed files
... ...
@@ -32,22 +32,23 @@ type Locker struct {
32 32
 type lockCtr struct {
33 33
 	mu sync.Mutex
34 34
 	// waiters is the number of waiters waiting to acquire the lock
35
-	waiters uint32
35
+	// this is int32 instead of uint32 so we can add `-1` in `dec()`
36
+	waiters int32
36 37
 }
37 38
 
38 39
 // inc increments the number of waiters waiting for the lock
39 40
 func (l *lockCtr) inc() {
40
-	atomic.AddUint32(&l.waiters, 1)
41
+	atomic.AddInt32(&l.waiters, 1)
41 42
 }
42 43
 
43 44
 // dec decrements the number of waiters wating on the lock
44 45
 func (l *lockCtr) dec() {
45
-	atomic.AddUint32(&l.waiters, ^uint32(l.waiters-1))
46
+	atomic.AddInt32(&l.waiters, -1)
46 47
 }
47 48
 
48 49
 // count gets the current number of waiters
49
-func (l *lockCtr) count() uint32 {
50
-	return atomic.LoadUint32(&l.waiters)
50
+func (l *lockCtr) count() int32 {
51
+	return atomic.LoadInt32(&l.waiters)
51 52
 }
52 53
 
53 54
 // Lock locks the mutex
... ...
@@ -1,6 +1,7 @@
1 1
 package locker
2 2
 
3 3
 import (
4
+	"sync"
4 5
 	"testing"
5 6
 	"time"
6 7
 )
... ...
@@ -89,3 +90,35 @@ func TestLockerUnlock(t *testing.T) {
89 89
 		t.Fatalf("lock should not be blocked")
90 90
 	}
91 91
 }
92
+
93
+func TestLockerConcurrency(t *testing.T) {
94
+	l := New()
95
+
96
+	var wg sync.WaitGroup
97
+	for i := 0; i <= 10000; i++ {
98
+		wg.Add(1)
99
+		go func() {
100
+			l.Lock("test")
101
+			// if there is a concurrency issue, will very likely panic here
102
+			l.Unlock("test")
103
+			wg.Done()
104
+		}()
105
+	}
106
+
107
+	chDone := make(chan struct{})
108
+	go func() {
109
+		wg.Wait()
110
+		close(chDone)
111
+	}()
112
+
113
+	select {
114
+	case <-chDone:
115
+	case <-time.After(10 * time.Second):
116
+		t.Fatal("timeout waiting for locks to complete")
117
+	}
118
+
119
+	// Since everything has unlocked this should not exist anymore
120
+	if ctr, exists := l.locks["test"]; exists {
121
+		t.Fatalf("lock should not exist: %v", ctr)
122
+	}
123
+}