-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathrefresh_concurrent_test.go
More file actions
138 lines (107 loc) · 2.93 KB
/
Copy pathrefresh_concurrent_test.go
File metadata and controls
138 lines (107 loc) · 2.93 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
package securityspy_test
import (
"net/http"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/require"
)
// TestRefreshConcurrentReaders is a race-detector test. Refresh() replaces
// Cameras, Groups and Info, and apps commonly call it from a background retry
// loop or an event handler while requests read those same fields. The Get
// accessors are the only safe way to read them, so the readers below run for
// as long as the refresher does.
//
// The workers report with t.Errorf rather than require: FailNow may only be
// called from the goroutine running the test.
func TestRefreshConcurrentReaders(t *testing.T) {
t.Parallel()
serverObj, _, _ := testServerWithCamera(t)
done := make(chan struct{})
var wait sync.WaitGroup
wait.Go(func() {
defer close(done)
for range 25 {
err := serverObj.Refresh()
if err != nil {
t.Errorf("Refresh: %v", err)
return
}
}
})
wait.Go(func() {
for !isDone(done) {
cams := serverObj.GetCameras()
if cams == nil {
t.Error("GetCameras returned nil during a refresh")
return
}
if cams.ByNum(3) == nil {
t.Error("camera 3 went missing during a refresh")
return
}
if len(cams.All()) == 0 {
t.Error("camera list emptied during a refresh")
return
}
}
})
wait.Go(func() {
for !isDone(done) {
info := serverObj.GetInfo()
if info == nil {
t.Error("GetInfo returned nil during a refresh")
return
}
_ = info.Version
_ = serverObj.GetGroups()
}
})
wait.Wait()
}
// TestRefreshDoesNotBlockReaders: a refresh holds the write lock only for the
// swap, so a slow (or hung) systemInfo request must not stall readers. The
// handler parks the second refresh mid-request; if the refresh held the lock
// across the round trip, the reads below would block until the test timed out.
func TestRefreshDoesNotBlockReaders(t *testing.T) {
t.Parallel()
var (
requests atomic.Int64
parked = make(chan struct{})
release = make(chan struct{})
)
serverObj := newTestServer(t, func(resp http.ResponseWriter, req *http.Request) {
if req.URL.Path != systemInfoPath {
http.NotFound(resp, req)
return
}
if requests.Add(1) > 1 { // let the first refresh load the snapshot
close(parked)
<-release
}
resp.Header().Set("Content-Type", "application/xml")
_, _ = resp.Write([]byte(testSystemInfoV6))
})
require.NoError(t, serverObj.Refresh())
refreshed := make(chan error, 1)
go func() { refreshed <- serverObj.Refresh() }()
<-parked
// The refresh is parked mid-request; reads still come from the old snapshot.
cams := serverObj.GetCameras()
if cams == nil || cams.ByNum(3) == nil {
t.Error("readers were blocked by an in-flight refresh")
}
if serverObj.GetInfo() == nil {
t.Error("GetInfo was blocked by an in-flight refresh")
}
close(release)
require.NoError(t, <-refreshed)
}
func isDone(done <-chan struct{}) bool {
select {
case <-done:
return true
default:
return false
}
}