Source file
src/runtime/synctest.go
1
2
3
4
5 package runtime
6
7 import (
8 "unsafe"
9 )
10
11
12 type synctestGroup struct {
13 mu mutex
14 timers timers
15 now int64
16 root *g
17 waiter *g
18 waiting bool
19
20
21
22
23
24
25
26
27
28
29
30
31 total int
32 running int
33 active int
34 }
35
36
37
38 func (sg *synctestGroup) changegstatus(gp *g, oldval, newval uint32) {
39
40
41
42
43
44
45
46
47 totalDelta := 0
48 wasRunning := true
49 switch oldval {
50 case _Gdead:
51 wasRunning = false
52 totalDelta++
53 case _Gwaiting:
54 if gp.waitreason.isIdleInSynctest() {
55 wasRunning = false
56 }
57 }
58 isRunning := true
59 switch newval {
60 case _Gdead:
61 isRunning = false
62 totalDelta--
63 case _Gwaiting:
64 if gp.waitreason.isIdleInSynctest() {
65 isRunning = false
66 }
67 }
68
69
70 if wasRunning == isRunning && totalDelta == 0 {
71 return
72 }
73
74 lock(&sg.mu)
75 sg.total += totalDelta
76 if wasRunning != isRunning {
77 if isRunning {
78 sg.running++
79 } else {
80 sg.running--
81 if raceenabled && newval != _Gdead {
82 racereleasemergeg(gp, sg.raceaddr())
83 }
84 }
85 }
86 if sg.total < 0 {
87 fatal("total < 0")
88 }
89 if sg.running < 0 {
90 fatal("running < 0")
91 }
92 wake := sg.maybeWakeLocked()
93 unlock(&sg.mu)
94 if wake != nil {
95 goready(wake, 0)
96 }
97 }
98
99
100
101 func (sg *synctestGroup) incActive() {
102 lock(&sg.mu)
103 sg.active++
104 unlock(&sg.mu)
105 }
106
107
108 func (sg *synctestGroup) decActive() {
109 lock(&sg.mu)
110 sg.active--
111 if sg.active < 0 {
112 throw("active < 0")
113 }
114 wake := sg.maybeWakeLocked()
115 unlock(&sg.mu)
116 if wake != nil {
117 goready(wake, 0)
118 }
119 }
120
121
122 func (sg *synctestGroup) maybeWakeLocked() *g {
123 if sg.running > 0 || sg.active > 0 {
124 return nil
125 }
126
127
128
129
130
131
132
133
134
135 sg.active++
136 if gp := sg.waiter; gp != nil {
137
138 return gp
139 }
140
141
142 return sg.root
143 }
144
145 func (sg *synctestGroup) raceaddr() unsafe.Pointer {
146
147
148
149
150 return unsafe.Pointer(sg)
151 }
152
153
154 func synctestRun(f func()) {
155 if debug.asynctimerchan.Load() != 0 {
156 panic("synctest.Run not supported with asynctimerchan!=0")
157 }
158
159 gp := getg()
160 if gp.syncGroup != nil {
161 panic("synctest.Run called from within a synctest bubble")
162 }
163 gp.syncGroup = &synctestGroup{
164 total: 1,
165 running: 1,
166 root: gp,
167 }
168 const synctestBaseTime = 946684800000000000
169 gp.syncGroup.now = synctestBaseTime
170 gp.syncGroup.timers.syncGroup = gp.syncGroup
171 lockInit(&gp.syncGroup.mu, lockRankSynctest)
172 lockInit(&gp.syncGroup.timers.mu, lockRankTimers)
173 defer func() {
174 gp.syncGroup = nil
175 }()
176
177 fv := *(**funcval)(unsafe.Pointer(&f))
178 newproc(fv)
179
180 sg := gp.syncGroup
181 lock(&sg.mu)
182 sg.active++
183 for {
184 if raceenabled {
185 raceacquireg(gp, gp.syncGroup.raceaddr())
186 }
187 unlock(&sg.mu)
188 systemstack(func() {
189 gp.syncGroup.timers.check(gp.syncGroup.now)
190 })
191 gopark(synctestidle_c, nil, waitReasonSynctestRun, traceBlockSynctest, 0)
192 lock(&sg.mu)
193 if sg.active < 0 {
194 throw("active < 0")
195 }
196 next := sg.timers.wakeTime()
197 if next == 0 {
198 break
199 }
200 if next < sg.now {
201 throw("time went backwards")
202 }
203 sg.now = next
204 }
205
206 total := sg.total
207 unlock(&sg.mu)
208 if total != 1 {
209 panic("deadlock: all goroutines in bubble are blocked")
210 }
211 if gp.timer != nil && gp.timer.isFake {
212
213
214 throw("synctest root goroutine has a fake timer")
215 }
216 }
217
218 func synctestidle_c(gp *g, _ unsafe.Pointer) bool {
219 lock(&gp.syncGroup.mu)
220 canIdle := true
221 if gp.syncGroup.running == 0 && gp.syncGroup.active == 1 {
222
223 canIdle = false
224 } else {
225 gp.syncGroup.active--
226 }
227 unlock(&gp.syncGroup.mu)
228 return canIdle
229 }
230
231
232 func synctestWait() {
233 gp := getg()
234 if gp.syncGroup == nil {
235 panic("goroutine is not in a bubble")
236 }
237 lock(&gp.syncGroup.mu)
238
239
240
241 if gp.syncGroup.waiting {
242 unlock(&gp.syncGroup.mu)
243 panic("wait already in progress")
244 }
245 gp.syncGroup.waiting = true
246 unlock(&gp.syncGroup.mu)
247 gopark(synctestwait_c, nil, waitReasonSynctestWait, traceBlockSynctest, 0)
248
249 lock(&gp.syncGroup.mu)
250 gp.syncGroup.active--
251 if gp.syncGroup.active < 0 {
252 throw("active < 0")
253 }
254 gp.syncGroup.waiter = nil
255 gp.syncGroup.waiting = false
256 unlock(&gp.syncGroup.mu)
257
258
259
260 if raceenabled {
261 raceacquireg(gp, gp.syncGroup.raceaddr())
262 }
263 }
264
265 func synctestwait_c(gp *g, _ unsafe.Pointer) bool {
266 lock(&gp.syncGroup.mu)
267 if gp.syncGroup.running == 0 && gp.syncGroup.active == 0 {
268
269 throw("running == 0 && active == 0")
270 }
271 gp.syncGroup.waiter = gp
272 unlock(&gp.syncGroup.mu)
273 return true
274 }
275
276
277 func synctest_acquire() any {
278 if sg := getg().syncGroup; sg != nil {
279 sg.incActive()
280 return sg
281 }
282 return nil
283 }
284
285
286 func synctest_release(sg any) {
287 sg.(*synctestGroup).decActive()
288 }
289
290
291 func synctest_inBubble(sg any, f func()) {
292 gp := getg()
293 if gp.syncGroup != nil {
294 panic("goroutine is already bubbled")
295 }
296 gp.syncGroup = sg.(*synctestGroup)
297 defer func() {
298 gp.syncGroup = nil
299 }()
300 f()
301 }
302
View as plain text