-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrepro_test.go
More file actions
140 lines (127 loc) · 3.6 KB
/
Copy pathrepro_test.go
File metadata and controls
140 lines (127 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package mb
import (
"context"
"errors"
"sync/atomic"
"testing"
"time"
)
// TestWaiterReuseStress stresses concurrent filtered waiters against a producer.
// Before waiter release was moved to the receiving side, trySendHeap marked a
// waiter reusable at send time, so a new consumer could be allocated the same
// waiter and steal a batch matched against the previous consumer's filter
// (data corruption), or trySendHeap could block on the full channel while
// holding mb.mu (deadlock). The test asserts full delivery with matching
// filters and fails by deadline instead of hanging if messages get lost.
func TestWaiterReuseStress(t *testing.T) {
q := New[int](0)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const (
consumers = 10
num = 1000
)
var mismatches, received int32
for i := 0; i < consumers; i++ {
go func(fv int) {
for {
msgs, err := q.NewCond().WithFilter(func(v int) bool {
return v%consumers == fv
}).Wait(ctx)
if err != nil {
return
}
for _, m := range msgs {
if m%consumers != fv {
atomic.AddInt32(&mismatches, 1)
}
}
atomic.AddInt32(&received, int32(len(msgs)))
}
}(i)
}
for i := 0; i < num; i++ {
if err := q.Add(ctx, i); err != nil {
t.Fatalf("Add: %v", err)
}
}
deadline := time.After(5 * time.Second)
for atomic.LoadInt32(&received) < num {
select {
case <-deadline:
t.Fatalf("timeout: %d/%d messages delivered (lost messages or deadlock)", atomic.LoadInt32(&received), num)
case <-time.After(time.Millisecond):
}
}
if n := atomic.LoadInt32(&mismatches); n > 0 {
t.Errorf("data corruption: %d messages delivered to a consumer whose filter did not match", n)
}
}
// waitAddBlocked waits until an Add call is parked waiting for free space.
func waitAddBlocked(t *testing.T, q *MB[int]) {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for {
q.mu.Lock()
waiting := q.addUnlock != nil
q.mu.Unlock()
if waiting {
return
}
if time.Now().After(deadline) {
t.Fatal("Add never blocked on the full queue")
}
time.Sleep(time.Millisecond)
}
}
// assertBlockedAddUnblocks blocks an Add on a full queue, applies the trigger
// and checks that Add returns wantErr instead of hanging forever.
func assertBlockedAddUnblocks(t *testing.T, trigger func(q *MB[int]), wantErr error) {
t.Helper()
q := New[int](1)
if err := q.Add(context.Background(), 1); err != nil {
t.Fatalf("Add: %v", err)
}
errCh := make(chan error, 1)
go func() {
errCh <- q.Add(context.Background(), 2)
}()
waitAddBlocked(t, q)
trigger(q)
select {
case err := <-errCh:
if !errors.Is(err, wantErr) {
t.Errorf("Add returned %v, want %v", err, wantErr)
}
case <-time.After(2 * time.Second):
t.Error("Add is still blocked after the trigger")
}
}
func TestAddUnblocksOnClose(t *testing.T) {
assertBlockedAddUnblocks(t, func(q *MB[int]) { q.Close() }, ErrClosed)
}
func TestAddUnblocksOnGetAll(t *testing.T) {
assertBlockedAddUnblocks(t, func(q *MB[int]) { q.GetAll() }, nil)
}
func TestStatsCounts(t *testing.T) {
q := New[int](0)
if err := q.Add(context.Background(), 1, 2); err != nil {
t.Fatalf("Add: %v", err)
}
if _, err := q.Wait(context.Background()); err != nil {
t.Fatalf("Wait: %v", err)
}
addCount, addMsgsCount, getCount, getMsgsCount := q.Stats()
if addCount != 1 {
t.Errorf("expected addCount 1, got %d", addCount)
}
if addMsgsCount != 2 {
t.Errorf("expected addMsgsCount 2, got %d", addMsgsCount)
}
if getCount != 1 {
t.Errorf("expected getCount 1, got %d", getCount)
}
if getMsgsCount != 2 {
t.Errorf("expected getMsgsCount 2, got %d", getMsgsCount)
}
}