state uint8 // state bits
isChan bool // timer has a channel; immutable; can be read without lock
- // isSending is used to handle races between running a
- // channel timer and stopping or resetting the timer.
- // It is used only for channel timers (t.isChan == true).
- // It is not used for tickers.
- // The lowest zero bit is set when about to send a value on the channel,
- // and cleared after sending the value.
- // The stop/reset code uses this to detect whether it
- // stopped the channel send.
- //
- // An isSending bit is set only when t.mu is held.
- // An isSending bit is cleared only when t.sendLock is held.
- // isSending is read only when both t.mu and t.sendLock are held.
- //
- // Setting and clearing Uint8 bits handles the case of
- // a timer that is reset concurrently with unlockAndRun.
- // If the reset timer runs immediately, we can wind up with
- // concurrent calls to unlockAndRun for the same timer.
- // Using matched bit set and clear in unlockAndRun
- // ensures that the value doesn't get temporarily out of sync.
- //
- // We use a uint8 to keep the timer struct small.
- // This means that we can only support up to 8 concurrent
- // runs of a timer, where a concurrent run can only occur if
- // we start a run, unlock the timer, the timer is reset to a new
- // value (or the ticker fires again), it is ready to run,
- // and it is actually run, all before the first run completes.
- // Since completing a run is fast, even 2 concurrent timer runs are
- // nearly impossible, so this should be safe in practice.
- isSending atomic.Uint8
-
blocked uint32 // number of goroutines blocked on timer's channel
// Timer wakes up at when, and then at when+period, ... (period > 0 only)
// sendLock protects sends on the timer's channel.
// Not used for async (pre-Go 1.23) behavior when debug.asynctimerchan.Load() != 0.
sendLock mutex
+
+ // isSending is used to handle races between running a
+ // channel timer and stopping or resetting the timer.
+ // It is used only for channel timers (t.isChan == true).
+ // It is not used for tickers.
+ // The value is incremented when about to send a value on the channel,
+ // and decremented after sending the value.
+ // The stop/reset code uses this to detect whether it
+ // stopped the channel send.
+ //
+ // isSending is incremented only when t.mu is held.
+ // isSending is decremented only when t.sendLock is held.
+ // isSending is read only when both t.mu and t.sendLock are held.
+ isSending atomic.Int32
}
// init initializes a newly allocated timer t.
}
async := debug.asynctimerchan.Load() != 0
- var isSendingClear uint8
if !async && t.isChan && t.period == 0 {
// Tell Stop/Reset that we are sending a value.
- // Set the lowest zero bit.
- // We do this awkward step because atomic.Uint8
- // doesn't support Add or CompareAndSwap.
- // We only set bits with t locked.
- v := t.isSending.Load()
- i := sys.TrailingZeros8(^v)
- if i == 8 {
+ if t.isSending.Add(1) < 0 {
throw("too many concurrent timer firings")
}
- isSendingClear = 1 << i
- t.isSending.Or(isSendingClear)
}
t.unlock()
// We are committed to possibly sending a value
// based on seq, so no need to keep telling
// stop/modify that we are sending.
- t.isSending.And(^isSendingClear)
+ if t.isSending.Add(-1) < 0 {
+ throw("mismatched isSending updates")
+ }
}
if t.seq != seq {
wg.Wait()
}
-// Test having a large number of goroutines wake up a timer simultaneously.
+// Test having a large number of goroutines wake up a ticker simultaneously.
// This used to trigger a crash when run under x/tools/cmd/stress.
-func TestMultiWakeup(t *testing.T) {
+func TestMultiWakeupTicker(t *testing.T) {
if testing.Short() {
t.Skip("-short")
}
wg.Wait()
}
+// Test having a large number of goroutines wake up a timer simultaneously.
+// This used to trigger a crash when run under x/tools/cmd/stress.
+func TestMultiWakeupTimer(t *testing.T) {
+ if testing.Short() {
+ t.Skip("-short")
+ }
+
+ goroutines := runtime.GOMAXPROCS(0)
+ timer := NewTimer(Nanosecond)
+ var wg sync.WaitGroup
+ wg.Add(goroutines)
+ for range goroutines {
+ go func() {
+ defer wg.Done()
+ for range 10000 {
+ select {
+ case <-timer.C:
+ default:
+ }
+ timer.Reset(Nanosecond)
+ }
+ }()
+ }
+ wg.Wait()
+}
+
// Benchmark timer latency when the thread that creates the timer is busy with
// other work and the timers must be serviced by other threads.
// https://golang.org/issue/38860