@@ -157,8 +157,6 @@ type timers struct {
157
157
// heap[i].when over timers with the timerModified bit set.
158
158
// If minWhenModified = 0, it means there are no timerModified timers in the heap.
159
159
minWhenModified atomic.Int64
160
-
161
- bubble * synctestBubble
162
160
}
163
161
164
162
type timerWhen struct {
@@ -403,7 +401,7 @@ func newTimer(when, period int64, f func(arg any, seq uintptr, delay int64), arg
403
401
throw ("invalid timer channel: no capacity" )
404
402
}
405
403
}
406
- if gr := getg ().bubble ; gr != nil {
404
+ if bubble := getg ().bubble ; bubble != nil {
407
405
t .isFake = true
408
406
}
409
407
t .modify (when , period , f , arg , 0 )
@@ -485,7 +483,7 @@ func (t *timer) maybeRunAsync() {
485
483
// timer ourselves now is fine.)
486
484
if now := nanotime (); t .when <= now {
487
485
systemstack (func () {
488
- t .unlockAndRun (now ) // resets t.when
486
+ t .unlockAndRun (now , nil ) // resets t.when
489
487
})
490
488
t .lock ()
491
489
}
@@ -621,6 +619,29 @@ func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay in
621
619
622
620
add := t .needsAdd ()
623
621
622
+ if add && t .isFake {
623
+ // If this is a bubbled timer scheduled to fire immediately,
624
+ // run it now rather than waiting for the bubble's timer scheduler.
625
+ // This avoids deferring timer execution until after the bubble
626
+ // becomes durably blocked.
627
+ //
628
+ // Don't do this for non-bubbled timers: It isn't necessary,
629
+ // and there may be cases where the runtime executes timers with
630
+ // the expectation the timer func will not run in the current goroutine.
631
+ // Bubbled timers are always created by the time package, and are
632
+ // safe to run in the current goroutine.
633
+ bubble := getg ().bubble
634
+ if bubble == nil {
635
+ throw ("fake timer executing with no bubble" )
636
+ }
637
+ if t .state & timerHeaped == 0 && when <= bubble .now {
638
+ systemstack (func () {
639
+ t .unlockAndRun (bubble .now , bubble )
640
+ })
641
+ return pending
642
+ }
643
+ }
644
+
624
645
if ! async && t .isChan {
625
646
// Stop any future sends with stale values.
626
647
// See timer.unlockAndRun.
@@ -657,7 +678,7 @@ func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay in
657
678
// t must be locked.
658
679
func (t * timer ) needsAdd () bool {
659
680
assertLockHeld (& t .mu )
660
- need := t .state & timerHeaped == 0 && t .when > 0 && (! t .isChan || t .isFake || t . blocked > 0 )
681
+ need := t .state & timerHeaped == 0 && t .when > 0 && (! t .isChan || t .blocked > 0 )
661
682
if need {
662
683
t .trace ("needsAdd+" )
663
684
} else {
@@ -982,7 +1003,7 @@ func (ts *timers) wakeTime() int64 {
982
1003
// We pass now in and out to avoid extra calls of nanotime.
983
1004
//
984
1005
//go:yeswritebarrierrec
985
- func (ts * timers ) check (now int64 ) (rnow , pollUntil int64 , ran bool ) {
1006
+ func (ts * timers ) check (now int64 , bubble * synctestBubble ) (rnow , pollUntil int64 , ran bool ) {
986
1007
ts .trace ("check" )
987
1008
// If it's not yet time for the first timer, or the first adjusted
988
1009
// timer, then there is nothing to do.
@@ -1015,7 +1036,7 @@ func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool) {
1015
1036
ts .adjust (now , false )
1016
1037
for len (ts .heap ) > 0 {
1017
1038
// Note that runtimer may temporarily unlock ts.
1018
- if tw := ts .run (now ); tw != 0 {
1039
+ if tw := ts .run (now , bubble ); tw != 0 {
1019
1040
if tw > 0 {
1020
1041
pollUntil = tw
1021
1042
}
@@ -1047,7 +1068,7 @@ func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool) {
1047
1068
// If a timer is run, this will temporarily unlock ts.
1048
1069
//
1049
1070
//go:systemstack
1050
- func (ts * timers ) run (now int64 ) int64 {
1071
+ func (ts * timers ) run (now int64 , bubble * synctestBubble ) int64 {
1051
1072
ts .trace ("run" )
1052
1073
assertLockHeld (& ts .mu )
1053
1074
Redo:
@@ -1081,7 +1102,7 @@ Redo:
1081
1102
return t .when
1082
1103
}
1083
1104
1084
- t .unlockAndRun (now )
1105
+ t .unlockAndRun (now , bubble )
1085
1106
assertLockHeld (& ts .mu ) // t is unlocked now, but not ts
1086
1107
return 0
1087
1108
}
@@ -1092,7 +1113,7 @@ Redo:
1092
1113
// unlockAndRun returns with t unlocked and t.ts (re-)locked.
1093
1114
//
1094
1115
//go:systemstack
1095
- func (t * timer ) unlockAndRun (now int64 ) {
1116
+ func (t * timer ) unlockAndRun (now int64 , bubble * synctestBubble ) {
1096
1117
t .trace ("unlockAndRun" )
1097
1118
assertLockHeld (& t .mu )
1098
1119
if t .ts != nil {
@@ -1104,10 +1125,10 @@ func (t *timer) unlockAndRun(now int64) {
1104
1125
// out from under us while this function executes.
1105
1126
gp := getg ()
1106
1127
var tsLocal * timers
1107
- if t . ts == nil || t . ts . bubble == nil {
1128
+ if bubble == nil {
1108
1129
tsLocal = & gp .m .p .ptr ().timers
1109
1130
} else {
1110
- tsLocal = & t . ts . bubble .timers
1131
+ tsLocal = & bubble .timers
1111
1132
}
1112
1133
if tsLocal .raceCtx == 0 {
1113
1134
tsLocal .raceCtx = racegostart (abi .FuncPCABIInternal ((* timers ).run ) + sys .PCQuantum )
@@ -1160,25 +1181,25 @@ func (t *timer) unlockAndRun(now int64) {
1160
1181
if gp .racectx != 0 {
1161
1182
throw ("unexpected racectx" )
1162
1183
}
1163
- if ts == nil || ts . bubble == nil {
1184
+ if bubble == nil {
1164
1185
gp .racectx = gp .m .p .ptr ().timers .raceCtx
1165
1186
} else {
1166
- gp .racectx = ts . bubble .timers .raceCtx
1187
+ gp .racectx = bubble .timers .raceCtx
1167
1188
}
1168
1189
}
1169
1190
1170
1191
if ts != nil {
1171
1192
ts .unlock ()
1172
1193
}
1173
1194
1174
- if ts != nil && ts . bubble != nil {
1195
+ if bubble != nil {
1175
1196
// Temporarily use the timer's synctest group for the G running this timer.
1176
1197
gp := getg ()
1177
1198
if gp .bubble != nil {
1178
1199
throw ("unexpected syncgroup set" )
1179
1200
}
1180
- gp .bubble = ts . bubble
1181
- ts . bubble .changegstatus (gp , _Gdead , _Grunning )
1201
+ gp .bubble = bubble
1202
+ bubble .changegstatus (gp , _Gdead , _Grunning )
1182
1203
}
1183
1204
1184
1205
if ! async && t .isChan {
@@ -1222,13 +1243,13 @@ func (t *timer) unlockAndRun(now int64) {
1222
1243
unlock (& t .sendLock )
1223
1244
}
1224
1245
1225
- if ts != nil && ts . bubble != nil {
1246
+ if bubble != nil {
1226
1247
gp := getg ()
1227
- ts . bubble .changegstatus (gp , _Grunning , _Gdead )
1248
+ bubble .changegstatus (gp , _Grunning , _Gdead )
1228
1249
if raceenabled {
1229
1250
// Establish a happens-before between this timer event and
1230
1251
// the next synctest.Wait call.
1231
- racereleasemergeg (gp , ts . bubble .raceaddr ())
1252
+ racereleasemergeg (gp , bubble .raceaddr ())
1232
1253
}
1233
1254
gp .bubble = nil
1234
1255
}
@@ -1415,24 +1436,10 @@ func badTimer() {
1415
1436
// maybeRunChan checks whether the timer needs to run
1416
1437
// to send a value to its associated channel. If so, it does.
1417
1438
// The timer must not be locked.
1418
- func (t * timer ) maybeRunChan () {
1419
- if t .isFake {
1420
- t .lock ()
1421
- var timerBubble * synctestBubble
1422
- if t .ts != nil {
1423
- timerBubble = t .ts .bubble
1424
- }
1425
- t .unlock ()
1426
- bubble := getg ().bubble
1427
- if bubble == nil {
1428
- panic (plainError ("synctest timer accessed from outside bubble" ))
1429
- }
1430
- if timerBubble != nil && bubble != timerBubble {
1431
- panic (plainError ("timer moved between synctest bubbles" ))
1432
- }
1433
- // No need to do anything here.
1434
- // synctest.Run will run the timer when it advances its fake clock.
1435
- return
1439
+ func (t * timer ) maybeRunChan (c * hchan ) {
1440
+ if t .isFake && getg ().bubble != c .bubble {
1441
+ // This should have been checked by the caller, but check just in case.
1442
+ fatal ("synctest timer accessed from outside bubble" )
1436
1443
}
1437
1444
if t .astate .Load ()& timerHeaped != 0 {
1438
1445
// If the timer is in the heap, the ordinary timer code
@@ -1442,6 +1449,9 @@ func (t *timer) maybeRunChan() {
1442
1449
1443
1450
t .lock ()
1444
1451
now := nanotime ()
1452
+ if t .isFake {
1453
+ now = getg ().bubble .now
1454
+ }
1445
1455
if t .state & timerHeaped != 0 || t .when == 0 || t .when > now {
1446
1456
t .trace ("maybeRunChan-" )
1447
1457
// Timer in the heap, or not running at all, or not triggered.
@@ -1450,7 +1460,7 @@ func (t *timer) maybeRunChan() {
1450
1460
}
1451
1461
t .trace ("maybeRunChan+" )
1452
1462
systemstack (func () {
1453
- t .unlockAndRun (now )
1463
+ t .unlockAndRun (now , c . bubble )
1454
1464
})
1455
1465
}
1456
1466
@@ -1460,9 +1470,11 @@ func (t *timer) maybeRunChan() {
1460
1470
// adding it if needed.
1461
1471
func blockTimerChan (c * hchan ) {
1462
1472
t := c .timer
1463
- if t .isFake {
1464
- return
1473
+ if t .isFake && c .bubble != getg ().bubble {
1474
+ // This should have been checked by the caller, but check just in case.
1475
+ fatal ("synctest timer accessed from outside bubble" )
1465
1476
}
1477
+
1466
1478
t .lock ()
1467
1479
t .trace ("blockTimerChan" )
1468
1480
if ! t .isChan {
@@ -1500,9 +1512,6 @@ func blockTimerChan(c *hchan) {
1500
1512
// blocked on it anymore.
1501
1513
func unblockTimerChan (c * hchan ) {
1502
1514
t := c .timer
1503
- if t .isFake {
1504
- return
1505
- }
1506
1515
t .lock ()
1507
1516
t .trace ("unblockTimerChan" )
1508
1517
if ! t .isChan || t .blocked == 0 {
0 commit comments