Source file
src/runtime/synctest.go
1
2
3
4
5 package runtime
6
7 import (
8 "internal/runtime/atomic"
9 "internal/runtime/sys"
10 "unsafe"
11 )
12
13
14 type synctestBubble struct {
15 mu mutex
16 timers timers
17 id uint64
18 now int64
19 root *g
20 waiter *g
21 main *g
22 waiting bool
23 done bool
24
25
26
27
28
29
30
31
32
33
34
35
36 total int
37 running int
38 active int
39 }
40
41
42
43 func (bubble *synctestBubble) changegstatus(gp *g, oldval, newval uint32) {
44
45
46
47
48
49
50
51
52 totalDelta := 0
53 wasRunning := true
54 switch oldval {
55 case _Gdead:
56 wasRunning = false
57 totalDelta++
58 case _Gwaiting:
59 if gp.waitreason.isIdleInSynctest() {
60 wasRunning = false
61 }
62 }
63 isRunning := true
64 switch newval {
65 case _Gdead:
66 isRunning = false
67 totalDelta--
68 if gp == bubble.main {
69 bubble.done = true
70 }
71 case _Gwaiting:
72 if gp.waitreason.isIdleInSynctest() {
73 isRunning = false
74 }
75 }
76
77
78 if wasRunning == isRunning && totalDelta == 0 {
79 return
80 }
81
82 lock(&bubble.mu)
83 bubble.total += totalDelta
84 if wasRunning != isRunning {
85 if isRunning {
86 bubble.running++
87 } else {
88 bubble.running--
89 if raceenabled && newval != _Gdead {
90
91
92 racereleasemergeg(gp, bubble.raceaddr())
93 }
94 }
95 }
96 if bubble.total < 0 {
97 fatal("total < 0")
98 }
99 if bubble.running < 0 {
100 fatal("running < 0")
101 }
102 wake := bubble.maybeWakeLocked()
103 unlock(&bubble.mu)
104 if wake != nil {
105 goready(wake, 0)
106 }
107 }
108
109
110
111 func (bubble *synctestBubble) incActive() {
112 lock(&bubble.mu)
113 bubble.active++
114 unlock(&bubble.mu)
115 }
116
117
118 func (bubble *synctestBubble) decActive() {
119 lock(&bubble.mu)
120 bubble.active--
121 if bubble.active < 0 {
122 throw("active < 0")
123 }
124 wake := bubble.maybeWakeLocked()
125 unlock(&bubble.mu)
126 if wake != nil {
127 goready(wake, 0)
128 }
129 }
130
131
132 func (bubble *synctestBubble) maybeWakeLocked() *g {
133 if bubble.running > 0 || bubble.active > 0 {
134 return nil
135 }
136
137
138
139
140
141
142
143
144
145 bubble.active++
146 next := bubble.timers.wakeTime()
147 if next > 0 && next <= bubble.now {
148
149 return bubble.root
150 }
151 if gp := bubble.waiter; gp != nil {
152
153 return gp
154 }
155
156
157 return bubble.root
158 }
159
160 func (bubble *synctestBubble) raceaddr() unsafe.Pointer {
161
162
163
164
165 return unsafe.Pointer(bubble)
166 }
167
168 var bubbleGen atomic.Uint64
169
170
171 func synctestRun(f func()) {
172 if debug.asynctimerchan.Load() != 0 {
173 panic("synctest.Run not supported with asynctimerchan!=0")
174 }
175
176 gp := getg()
177 if gp.bubble != nil {
178 panic("synctest.Run called from within a synctest bubble")
179 }
180 bubble := &synctestBubble{
181 id: bubbleGen.Add(1),
182 total: 1,
183 running: 1,
184 root: gp,
185 }
186 const synctestBaseTime = 946684800000000000
187 bubble.now = synctestBaseTime
188 lockInit(&bubble.mu, lockRankSynctest)
189 lockInit(&bubble.timers.mu, lockRankTimers)
190
191 gp.bubble = bubble
192 defer func() {
193 gp.bubble = nil
194 }()
195
196
197 pc := sys.GetCallerPC()
198 systemstack(func() {
199 fv := *(**funcval)(unsafe.Pointer(&f))
200 bubble.main = newproc1(fv, gp, pc, false, waitReasonZero)
201 pp := getg().m.p.ptr()
202 runqput(pp, bubble.main, true)
203 wakep()
204 })
205
206 lock(&bubble.mu)
207 bubble.active++
208 for {
209 unlock(&bubble.mu)
210 systemstack(func() {
211
212
213 curg := gp.m.curg
214 gp.m.curg = nil
215 gp.bubble.timers.check(bubble.now, bubble)
216 gp.m.curg = curg
217 })
218 gopark(synctestidle_c, nil, waitReasonSynctestRun, traceBlockSynctest, 0)
219 lock(&bubble.mu)
220 if bubble.active < 0 {
221 throw("active < 0")
222 }
223 next := bubble.timers.wakeTime()
224 if next == 0 {
225 break
226 }
227 if next < bubble.now {
228 throw("time went backwards")
229 }
230 if bubble.done {
231
232 break
233 }
234 bubble.now = next
235 }
236
237 total := bubble.total
238 unlock(&bubble.mu)
239 if raceenabled {
240
241
242 raceacquireg(gp, gp.bubble.raceaddr())
243 }
244 if total != 1 {
245 var reason string
246 if bubble.done {
247 reason = "deadlock: main bubble goroutine has exited but blocked goroutines remain"
248 } else {
249 reason = "deadlock: all goroutines in bubble are blocked"
250 }
251 panic(synctestDeadlockError{reason: reason, bubble: bubble})
252 }
253 if gp.timer != nil && gp.timer.isFake {
254
255
256 throw("synctest root goroutine has a fake timer")
257 }
258 }
259
260 type synctestDeadlockError struct {
261 reason string
262 bubble *synctestBubble
263 }
264
265 func (e synctestDeadlockError) Error() string {
266 return e.reason
267 }
268
269 func synctestidle_c(gp *g, _ unsafe.Pointer) bool {
270 lock(&gp.bubble.mu)
271 canIdle := true
272 if gp.bubble.running == 0 && gp.bubble.active == 1 {
273
274 canIdle = false
275 } else {
276 gp.bubble.active--
277 }
278 unlock(&gp.bubble.mu)
279 return canIdle
280 }
281
282
283 func synctestWait() {
284 gp := getg()
285 if gp.bubble == nil {
286 panic("goroutine is not in a bubble")
287 }
288 lock(&gp.bubble.mu)
289
290
291
292 if gp.bubble.waiting {
293 unlock(&gp.bubble.mu)
294 panic("wait already in progress")
295 }
296 gp.bubble.waiting = true
297 unlock(&gp.bubble.mu)
298 gopark(synctestwait_c, nil, waitReasonSynctestWait, traceBlockSynctest, 0)
299
300 lock(&gp.bubble.mu)
301 gp.bubble.active--
302 if gp.bubble.active < 0 {
303 throw("active < 0")
304 }
305 gp.bubble.waiter = nil
306 gp.bubble.waiting = false
307 unlock(&gp.bubble.mu)
308
309
310
311 if raceenabled {
312 raceacquireg(gp, gp.bubble.raceaddr())
313 }
314 }
315
316 func synctestwait_c(gp *g, _ unsafe.Pointer) bool {
317 lock(&gp.bubble.mu)
318 if gp.bubble.running == 0 && gp.bubble.active == 0 {
319
320 throw("running == 0 && active == 0")
321 }
322 gp.bubble.waiter = gp
323 unlock(&gp.bubble.mu)
324 return true
325 }
326
327
328 func synctest_isInBubble() bool {
329 return getg().bubble != nil
330 }
331
332
333 func synctest_acquire() any {
334 if bubble := getg().bubble; bubble != nil {
335 bubble.incActive()
336 return bubble
337 }
338 return nil
339 }
340
341
342 func synctest_release(bubble any) {
343 bubble.(*synctestBubble).decActive()
344 }
345
346
347 func synctest_inBubble(bubble any, f func()) {
348 gp := getg()
349 if gp.bubble != nil {
350 panic("goroutine is already bubbled")
351 }
352 gp.bubble = bubble.(*synctestBubble)
353 defer func() {
354 gp.bubble = nil
355 }()
356 f()
357 }
358
359
360 type specialBubble struct {
361 _ sys.NotInHeap
362 special special
363 bubbleid uint64
364 }
365
366
367 const (
368 bubbleAssocUnbubbled = iota
369 bubbleAssocCurrentBubble
370 bubbleAssocOtherBubble
371 )
372
373
374
375
376
377
378
379
380
381 func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (assoc int) {
382 span := spanOfHeap(uintptr(p))
383 if span == nil {
384
385
386 return bubbleAssocUnbubbled
387 }
388
389
390
391
392 mp := acquirem()
393 span.ensureSwept()
394
395 offset := uintptr(p) - span.base()
396
397 lock(&span.speciallock)
398
399
400 iter, exists := span.specialFindSplicePoint(offset, _KindSpecialBubble)
401 if exists {
402
403
404 s := (*specialBubble)((unsafe.Pointer)(*iter))
405 if s.bubbleid == bubbleid {
406 assoc = bubbleAssocCurrentBubble
407 } else {
408 assoc = bubbleAssocOtherBubble
409 }
410 } else if add {
411
412
413 s := (*specialBubble)(mheap_.specialBubbleAlloc.alloc())
414 s.bubbleid = bubbleid
415 s.special.kind = _KindSpecialBubble
416 s.special.offset = offset
417 s.special.next = *iter
418 *iter = (*special)(unsafe.Pointer(s))
419 spanHasSpecials(span)
420 assoc = bubbleAssocCurrentBubble
421 } else {
422
423 assoc = bubbleAssocUnbubbled
424 }
425
426 unlock(&span.speciallock)
427 releasem(mp)
428
429 return assoc
430 }
431
432
433
434
435
436 func synctest_associate(p unsafe.Pointer) int {
437 return getOrSetBubbleSpecial(p, getg().bubble.id, true)
438 }
439
440
441
442
443 func synctest_disassociate(p unsafe.Pointer) {
444 removespecial(p, _KindSpecialBubble)
445 }
446
447
448
449
450 func synctest_isAssociated(p unsafe.Pointer) bool {
451 return getOrSetBubbleSpecial(p, getg().bubble.id, false) == bubbleAssocCurrentBubble
452 }
453
View as plain text