// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package time_test

import (
	"errors"
	"fmt"
	"internal/testenv"
	"math/rand"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	. "time"
	_ "unsafe" // for go:linkname
)

// newTimerFunc simulates NewTimer using AfterFunc,
// but this version will not hit the special cases for channels
// that are used when calling NewTimer.
// This makes it easy to test both paths.
func newTimerFunc(d Duration) *Timer {
	c := make(chan Time, 1)
	t := AfterFunc(d, func() { c <- Now() })
	t.C = c
	return t
}

// haveHighResSleep is true if the system supports at least ~1ms sleeps.
//
//go:linkname haveHighResSleep runtime.haveHighResSleep
var haveHighResSleep bool

// adjustDelay returns an adjusted delay based on the system sleep resolution.
// Go runtime uses different Windows timers for time.Now and sleeping.
// These can tick at different frequencies and can arrive out of sync.
// The effect can be seen, for example, as time.Sleep(100ms) is actually
// shorter then 100ms when measured as difference between time.Now before and
// after time.Sleep call. This was observed on Windows XP SP3 (windows/386).
func adjustDelay(t *testing.T, delay Duration) Duration {
	if haveHighResSleep {
		return delay
	}
	t.Log("adjusting delay for low resolution sleep")
	switch runtime.GOOS {
	case "windows":
		return delay - 17*Millisecond
	default:
		t.Fatal("adjustDelay unimplemented on " + runtime.GOOS)
		return 0
	}
}

func TestSleep(t *testing.T) {
	const delay = 100 * Millisecond
	go func() {
		Sleep(delay / 2)
		Interrupt()
	}()
	start := Now()
	Sleep(delay)
	delayadj := adjustDelay(t, delay)
	duration := Since(start)
	if duration < delayadj {
		t.Fatalf("Sleep(%s) slept for only %s", delay, duration)
	}
}

// Test the basic function calling behavior. Correct queuing
// behavior is tested elsewhere, since After and AfterFunc share
// the same code.
func TestAfterFunc(t *testing.T) {
	i := 10
	c := make(chan bool)
	var f func()
	f = func() {
		i--
		if i >= 0 {
			AfterFunc(0, f)
			Sleep(1 * Second)
		} else {
			c <- true
		}
	}

	AfterFunc(0, f)
	<-c
}

func TestTickerStress(t *testing.T) {
	var stop atomic.Bool
	go func() {
		for !stop.Load() {
			runtime.GC()
			// Yield so that the OS can wake up the timer thread,
			// so that it can generate channel sends for the main goroutine,
			// which will eventually set stop = 1 for us.
			Sleep(Nanosecond)
		}
	}()
	ticker := NewTicker(1)
	for i := 0; i < 100; i++ {
		<-ticker.C
	}
	ticker.Stop()
	stop.Store(true)
}

func TestTickerConcurrentStress(t *testing.T) {
	var stop atomic.Bool
	go func() {
		for !stop.Load() {
			runtime.GC()
			// Yield so that the OS can wake up the timer thread,
			// so that it can generate channel sends for the main goroutine,
			// which will eventually set stop = 1 for us.
			Sleep(Nanosecond)
		}
	}()
	ticker := NewTicker(1)
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := 0; i < 100; i++ {
				<-ticker.C
			}
		}()
	}
	wg.Wait()
	ticker.Stop()
	stop.Store(true)
}

func TestAfterFuncStarvation(t *testing.T) {
	// Start two goroutines ping-ponging on a channel send.
	// At any given time, at least one of these goroutines is runnable:
	// if the channel buffer is full, the receiver is runnable,
	// and if it is not full, the sender is runnable.
	//
	// In addition, the AfterFunc callback should become runnable after
	// the indicated delay.
	//
	// Even if GOMAXPROCS=1, we expect the runtime to eventually schedule
	// the AfterFunc goroutine instead of the runnable channel goroutine.
	// However, in https://go.dev/issue/65178 this was observed to live-lock
	// on wasip1/wasm and js/wasm after <10000 runs.
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))

	var (
		wg   sync.WaitGroup
		stop atomic.Bool
		c    = make(chan bool, 1)
	)

	wg.Add(2)
	go func() {
		for !stop.Load() {
			c <- true
		}
		close(c)
		wg.Done()
	}()
	go func() {
		for range c {
		}
		wg.Done()
	}()

	AfterFunc(1*Microsecond, func() { stop.Store(true) })
	wg.Wait()
}

func benchmark(b *testing.B, bench func(*testing.PB)) {
	// Create equal number of garbage timers on each P before starting
	// the benchmark.
	var wg sync.WaitGroup
	garbageAll := make([][]*Timer, runtime.GOMAXPROCS(0))
	for i := range garbageAll {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			garbage := make([]*Timer, 1<<15)
			for j := range garbage {
				garbage[j] = AfterFunc(Hour, nil)
			}
			garbageAll[i] = garbage
		}(i)
	}
	wg.Wait()

	b.ResetTimer()
	b.RunParallel(bench)
	b.StopTimer()

	for _, garbage := range garbageAll {
		for _, t := range garbage {
			t.Stop()
		}
	}
}

func BenchmarkAfterFunc1000(b *testing.B) {
	benchmark(b, func(pb *testing.PB) {
		for pb.Next() {
			n := 1000
			c := make(chan bool)
			var f func()
			f = func() {
				n--
				if n >= 0 {
					AfterFunc(0, f)
				} else {
					c <- true
				}
			}
			AfterFunc(0, f)
			<-c
		}
	})
}

func BenchmarkAfter(b *testing.B) {
	benchmark(b, func(pb *testing.PB) {
		for pb.Next() {
			<-After(1)
		}
	})
}

func BenchmarkStop(b *testing.B) {
	b.Run("impl=chan", func(b *testing.B) {
		benchmark(b, func(pb *testing.PB) {
			for pb.Next() {
				NewTimer(1 * Second).Stop()
			}
		})
	})
	b.Run("impl=func", func(b *testing.B) {
		benchmark(b, func(pb *testing.PB) {
			for pb.Next() {
				newTimerFunc(1 * Second).Stop()
			}
		})
	})
}

func BenchmarkSimultaneousAfterFunc1000(b *testing.B) {
	benchmark(b, func(pb *testing.PB) {
		for pb.Next() {
			n := 1000
			var wg sync.WaitGroup
			wg.Add(n)
			for range n {
				AfterFunc(0, wg.Done)
			}
			wg.Wait()
		}
	})
}

func BenchmarkStartStop1000(b *testing.B) {
	benchmark(b, func(pb *testing.PB) {
		for pb.Next() {
			const N = 1000
			timers := make([]*Timer, N)
			for i := range timers {
				timers[i] = AfterFunc(Hour, nil)
			}

			for i := range timers {
				timers[i].Stop()
			}
		}
	})
}

func BenchmarkReset(b *testing.B) {
	b.Run("impl=chan", func(b *testing.B) {
		benchmark(b, func(pb *testing.PB) {
			t := NewTimer(Hour)
			for pb.Next() {
				t.Reset(Hour)
			}
			t.Stop()
		})
	})
	b.Run("impl=func", func(b *testing.B) {
		benchmark(b, func(pb *testing.PB) {
			t := newTimerFunc(Hour)
			for pb.Next() {
				t.Reset(Hour)
			}
			t.Stop()
		})
	})
}

func BenchmarkSleep1000(b *testing.B) {
	benchmark(b, func(pb *testing.PB) {
		for pb.Next() {
			const N = 1000
			var wg sync.WaitGroup
			wg.Add(N)
			for range N {
				go func() {
					Sleep(Nanosecond)
					wg.Done()
				}()
			}
			wg.Wait()
		}
	})
}

func TestAfter(t *testing.T) {
	const delay = 100 * Millisecond
	start := Now()
	end := <-After(delay)
	delayadj := adjustDelay(t, delay)
	if duration := Since(start); duration < delayadj {
		t.Fatalf("After(%s) slept for only %d ns", delay, duration)
	}
	if min := start.Add(delayadj); end.Before(min) {
		t.Fatalf("After(%s) expect >= %s, got %s", delay, min, end)
	}
}

func TestAfterTick(t *testing.T) {
	t.Parallel()
	const Count = 10
	Delta := 100 * Millisecond
	if testing.Short() {
		Delta = 10 * Millisecond
	}
	t0 := Now()
	for i := 0; i < Count; i++ {
		<-After(Delta)
	}
	t1 := Now()
	d := t1.Sub(t0)
	target := Delta * Count
	if d < target*9/10 {
		t.Fatalf("%d ticks of %s too fast: took %s, expected %s", Count, Delta, d, target)
	}
	if !testing.Short() && d > target*30/10 {
		t.Fatalf("%d ticks of %s too slow: took %s, expected %s", Count, Delta, d, target)
	}
}

func TestAfterStop(t *testing.T) {
	t.Run("impl=chan", func(t *testing.T) {
		testAfterStop(t, NewTimer)
	})
	t.Run("impl=func", func(t *testing.T) {
		testAfterStop(t, newTimerFunc)
	})
}

func testAfterStop(t *testing.T, newTimer func(Duration) *Timer) {
	// We want to test that we stop a timer before it runs.
	// We also want to test that it didn't run after a longer timer.
	// Since we don't want the test to run for too long, we don't
	// want to use lengthy times. That makes the test inherently flaky.
	// So only report an error if it fails five times in a row.

	var errs []string
	logErrs := func() {
		for _, e := range errs {
			t.Log(e)
		}
	}

	for i := 0; i < 5; i++ {
		AfterFunc(100*Millisecond, func() {})
		t0 := newTimer(50 * Millisecond)
		c1 := make(chan bool, 1)
		t1 := AfterFunc(150*Millisecond, func() { c1 <- true })
		c2 := After(200 * Millisecond)
		if !t0.Stop() {
			errs = append(errs, "failed to stop event 0")
			continue
		}
		if !t1.Stop() {
			errs = append(errs, "failed to stop event 1")
			continue
		}
		<-c2
		select {
		case <-t0.C:
			errs = append(errs, "event 0 was not stopped")
			continue
		case <-c1:
			errs = append(errs, "event 1 was not stopped")
			continue
		default:
		}
		if t1.Stop() {
			errs = append(errs, "Stop returned true twice")
			continue
		}

		// Test passed, so all done.
		if len(errs) > 0 {
			t.Logf("saw %d errors, ignoring to avoid flakiness", len(errs))
			logErrs()
		}

		return
	}

	t.Errorf("saw %d errors", len(errs))
	logErrs()
}

func TestAfterQueuing(t *testing.T) {
	t.Run("impl=chan", func(t *testing.T) {
		testAfterQueuing(t, After)
	})
	t.Run("impl=func", func(t *testing.T) {
		testAfterQueuing(t, func(d Duration) <-chan Time { return newTimerFunc(d).C })
	})
}

func testAfterQueuing(t *testing.T, after func(Duration) <-chan Time) {
	// This test flakes out on some systems,
	// so we'll try it a few times before declaring it a failure.
	const attempts = 5
	err := errors.New("!=nil")
	for i := 0; i < attempts && err != nil; i++ {
		delta := Duration(20+i*50) * Millisecond
		if err = testAfterQueuing1(delta, after); err != nil {
			t.Logf("attempt %v failed: %v", i, err)
		}
	}
	if err != nil {
		t.Fatal(err)
	}
}

var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8, 0}

type afterResult struct {
	slot int
	t    Time
}

func await(slot int, result chan<- afterResult, ac <-chan Time) {
	result <- afterResult{slot, <-ac}
}

func testAfterQueuing1(delta Duration, after func(Duration) <-chan Time) error {
	// make the result channel buffered because we don't want
	// to depend on channel queuing semantics that might
	// possibly change in the future.
	result := make(chan afterResult, len(slots))

	t0 := Now()
	for _, slot := range slots {
		go await(slot, result, After(Duration(slot)*delta))
	}
	var order []int
	var times []Time
	for range slots {
		r := <-result
		order = append(order, r.slot)
		times = append(times, r.t)
	}
	for i := range order {
		if i > 0 && order[i] < order[i-1] {
			return fmt.Errorf("After calls returned out of order: %v", order)
		}
	}
	for i, t := range times {
		dt := t.Sub(t0)
		target := Duration(order[i]) * delta
		if dt < target-delta/2 || dt > target+delta*10 {
			return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-delta/2, target+delta*10)
		}
	}
	return nil
}

func TestTimerStopStress(t *testing.T) {
	if testing.Short() {
		return
	}
	t.Parallel()
	for i := 0; i < 100; i++ {
		go func(i int) {
			timer := AfterFunc(2*Second, func() {
				t.Errorf("timer %d was not stopped", i)
			})
			Sleep(1 * Second)
			timer.Stop()
		}(i)
	}
	Sleep(3 * Second)
}

func TestSleepZeroDeadlock(t *testing.T) {
	// Sleep(0) used to hang, the sequence of events was as follows.
	// Sleep(0) sets G's status to Gwaiting, but then immediately returns leaving the status.
	// Then the goroutine calls e.g. new and falls down into the scheduler due to pending GC.
	// After the GC nobody wakes up the goroutine from Gwaiting status.
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
	c := make(chan bool)
	go func() {
		for i := 0; i < 100; i++ {
			runtime.GC()
		}
		c <- true
	}()
	for i := 0; i < 100; i++ {
		Sleep(0)
		tmp := make(chan bool, 1)
		tmp <- true
		<-tmp
	}
	<-c
}

func testReset(d Duration) error {
	t0 := NewTimer(2 * d)
	Sleep(d)
	if !t0.Reset(3 * d) {
		return errors.New("resetting unfired timer returned false")
	}
	Sleep(2 * d)
	select {
	case <-t0.C:
		return errors.New("timer fired early")
	default:
	}
	Sleep(2 * d)
	select {
	case <-t0.C:
	default:
		return errors.New("reset timer did not fire")
	}

	if t0.Reset(50 * Millisecond) {
		return errors.New("resetting expired timer returned true")
	}
	return nil
}

func TestReset(t *testing.T) {
	// We try to run this test with increasingly larger multiples
	// until one works so slow, loaded hardware isn't as flaky,
	// but without slowing down fast machines unnecessarily.
	//
	// (maxDuration is several orders of magnitude longer than we
	// expect this test to actually take on a fast, unloaded machine.)
	d := 1 * Millisecond
	const maxDuration = 10 * Second
	for {
		err := testReset(d)
		if err == nil {
			break
		}
		d *= 2
		if d > maxDuration {
			t.Error(err)
		}
		t.Logf("%v; trying duration %v", err, d)
	}
}

// Test that sleeping (via Sleep or Timer) for an interval so large it
// overflows does not result in a short sleep duration. Nor does it interfere
// with execution of other timers. If it does, timers in this or subsequent
// tests may not fire.
func TestOverflowSleep(t *testing.T) {
	const big = Duration(int64(1<<63 - 1))

	go func() {
		Sleep(big)
		// On failure, this may return after the test has completed, so
		// we need to panic instead.
		panic("big sleep returned")
	}()

	select {
	case <-After(big):
		t.Fatalf("big timeout fired")
	case <-After(25 * Millisecond):
		// OK
	}

	const neg = Duration(-1 << 63)
	Sleep(neg) // Returns immediately.
	select {
	case <-After(neg):
		// OK
	case <-After(1 * Second):
		t.Fatalf("negative timeout didn't fire")
	}
}

// Test that a panic while deleting a timer does not leave
// the timers mutex held, deadlocking a ticker.Stop in a defer.
func TestIssue5745(t *testing.T) {
	ticker := NewTicker(Hour)
	defer func() {
		// would deadlock here before the fix due to
		// lock taken before the segfault.
		ticker.Stop()

		if r := recover(); r == nil {
			t.Error("Expected panic, but none happened.")
		}
	}()

	// cause a panic due to a segfault
	var timer *Timer
	timer.Stop()
	t.Error("Should be unreachable.")
}

func TestOverflowPeriodRuntimeTimer(t *testing.T) {
	// This may hang forever if timers are broken. See comment near
	// the end of CheckRuntimeTimerOverflow in internal_test.go.
	CheckRuntimeTimerPeriodOverflow()
}

func checkZeroPanicString(t *testing.T) {
	e := recover()
	s, _ := e.(string)
	if want := "called on uninitialized Timer"; !strings.Contains(s, want) {
		t.Errorf("panic = %v; want substring %q", e, want)
	}
}

func TestZeroTimerResetPanics(t *testing.T) {
	defer checkZeroPanicString(t)
	var tr Timer
	tr.Reset(1)
}

func TestZeroTimerStopPanics(t *testing.T) {
	defer checkZeroPanicString(t)
	var tr Timer
	tr.Stop()
}

// Test that zero duration timers aren't missed by the scheduler. Regression test for issue 44868.
func TestZeroTimer(t *testing.T) {
	t.Run("impl=chan", func(t *testing.T) {
		testZeroTimer(t, NewTimer)
	})
	t.Run("impl=func", func(t *testing.T) {
		testZeroTimer(t, newTimerFunc)
	})
	t.Run("impl=cache", func(t *testing.T) {
		timer := newTimerFunc(Hour)
		testZeroTimer(t, func(d Duration) *Timer {
			timer.Reset(d)
			return timer
		})
	})
}

func testZeroTimer(t *testing.T, newTimer func(Duration) *Timer) {
	if testing.Short() {
		t.Skip("-short")
	}

	for i := 0; i < 1000000; i++ {
		s := Now()
		ti := newTimer(0)
		<-ti.C
		if diff := Since(s); diff > 2*Second {
			t.Errorf("Expected time to get value from Timer channel in less than 2 sec, took %v", diff)
		}
	}
}

// Test that rapidly moving a timer earlier doesn't cause it to get dropped.
// Issue 47329.
func TestTimerModifiedEarlier(t *testing.T) {
	if runtime.GOOS == "plan9" && runtime.GOARCH == "arm" {
		testenv.SkipFlaky(t, 50470)
	}

	past := Until(Unix(0, 0))
	count := 1000
	fail := 0
	for i := 0; i < count; i++ {
		timer := newTimerFunc(Hour)
		for j := 0; j < 10; j++ {
			if !timer.Stop() {
				<-timer.C
			}
			timer.Reset(past)
		}

		deadline := NewTimer(10 * Second)
		defer deadline.Stop()
		now := Now()
		select {
		case <-timer.C:
			if since := Since(now); since > 8*Second {
				t.Errorf("timer took too long (%v)", since)
				fail++
			}
		case <-deadline.C:
			t.Error("deadline expired")
		}
	}

	if fail > 0 {
		t.Errorf("%d failures", fail)
	}
}

// Test that rapidly moving timers earlier and later doesn't cause
// some of the sleep times to be lost.
// Issue 47762
func TestAdjustTimers(t *testing.T) {
	var rnd = rand.New(rand.NewSource(Now().UnixNano()))

	timers := make([]*Timer, 100)
	states := make([]int, len(timers))
	indices := rnd.Perm(len(timers))

	for len(indices) != 0 {
		var ii = rnd.Intn(len(indices))
		var i = indices[ii]

		var timer = timers[i]
		var state = states[i]
		states[i]++

		switch state {
		case 0:
			timers[i] = newTimerFunc(0)

		case 1:
			<-timer.C // Timer is now idle.

		// Reset to various long durations, which we'll cancel.
		case 2:
			if timer.Reset(1 * Minute) {
				panic("shouldn't be active (1)")
			}
		case 4:
			if timer.Reset(3 * Minute) {
				panic("shouldn't be active (3)")
			}
		case 6:
			if timer.Reset(2 * Minute) {
				panic("shouldn't be active (2)")
			}

		// Stop and drain a long-duration timer.
		case 3, 5, 7:
			if !timer.Stop() {
				t.Logf("timer %d state %d Stop returned false", i, state)
				<-timer.C
			}

		// Start a short-duration timer we expect to select without blocking.
		case 8:
			if timer.Reset(0) {
				t.Fatal("timer.Reset returned true")
			}
		case 9:
			now := Now()
			<-timer.C
			dur := Since(now)
			if dur > 750*Millisecond {
				t.Errorf("timer %d took %v to complete", i, dur)
			}

		// Timer is done. Swap with tail and remove.
		case 10:
			indices[ii] = indices[len(indices)-1]
			indices = indices[:len(indices)-1]
		}
	}
}

func TestStopResult(t *testing.T) {
	testStopResetResult(t, true)
}

func TestResetResult(t *testing.T) {
	testStopResetResult(t, false)
}

// Test that when racing between running a timer and stopping a timer Stop
// consistently indicates whether a value can be read from the channel.
// Issue #69312.
func testStopResetResult(t *testing.T, testStop bool) {
	for _, name := range []string{"0", "1", "2"} {
		t.Run("asynctimerchan="+name, func(t *testing.T) {
			testStopResetResultGODEBUG(t, testStop, name)
		})
	}
}

func testStopResetResultGODEBUG(t *testing.T, testStop bool, godebug string) {
	t.Setenv("GODEBUG", "asynctimerchan="+godebug)

	stopOrReset := func(timer *Timer) bool {
		if testStop {
			return timer.Stop()
		} else {
			return timer.Reset(1 * Hour)
		}
	}

	start := make(chan struct{})
	var wg sync.WaitGroup
	const N = 1000
	wg.Add(N)
	for range N {
		go func() {
			defer wg.Done()
			<-start
			for j := 0; j < 100; j++ {
				timer1 := NewTimer(1 * Millisecond)
				timer2 := NewTimer(1 * Millisecond)
				select {
				case <-timer1.C:
					if !stopOrReset(timer2) {
						// The test fails if this
						// channel read times out.
						<-timer2.C
					}
				case <-timer2.C:
					if !stopOrReset(timer1) {
						// The test fails if this
						// channel read times out.
						<-timer1.C
					}
				}
			}
		}()
	}
	close(start)
	wg.Wait()
}

// 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 TestMultiWakeupTicker(t *testing.T) {
	if testing.Short() {
		t.Skip("-short")
	}

	goroutines := runtime.GOMAXPROCS(0)
	timer := NewTicker(Microsecond)
	var wg sync.WaitGroup
	wg.Add(goroutines)
	for range goroutines {
		go func() {
			defer wg.Done()
			for range 100000 {
				select {
				case <-timer.C:
				case <-After(Millisecond):
				}
			}
		}()
	}
	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
func BenchmarkParallelTimerLatency(b *testing.B) {
	gmp := runtime.GOMAXPROCS(0)
	if gmp < 2 || runtime.NumCPU() < gmp {
		b.Skip("skipping with GOMAXPROCS < 2 or NumCPU < GOMAXPROCS")
	}

	// allocate memory now to avoid GC interference later.
	timerCount := gmp - 1
	stats := make([]struct {
		sum   float64
		max   Duration
		count int64
		_     [5]int64 // cache line padding
	}, timerCount)

	// Ensure the time to start new threads to service timers will not pollute
	// the results.
	warmupScheduler(gmp)

	// Note that other than the AfterFunc calls this benchmark is measuring it
	// avoids using any other timers. In particular, the main goroutine uses
	// doWork to spin for some durations because up through Go 1.15 if all
	// threads are idle sysmon could leave deep sleep when we wake.

	// Ensure sysmon is in deep sleep.
	doWork(30 * Millisecond)

	b.ResetTimer()

	const delay = Millisecond
	var wg sync.WaitGroup
	var count int32
	for i := 0; i < b.N; i++ {
		wg.Add(timerCount)
		atomic.StoreInt32(&count, 0)
		for j := 0; j < timerCount; j++ {
			j := j
			expectedWakeup := Now().Add(delay)
			AfterFunc(delay, func() {
				late := Since(expectedWakeup)
				if late < 0 {
					late = 0
				}
				stats[j].count++
				stats[j].sum += float64(late.Nanoseconds())
				if late > stats[j].max {
					stats[j].max = late
				}
				atomic.AddInt32(&count, 1)
				for atomic.LoadInt32(&count) < int32(timerCount) {
					// spin until all timers fired
				}
				wg.Done()
			})
		}

		for atomic.LoadInt32(&count) < int32(timerCount) {
			// spin until all timers fired
		}
		wg.Wait()

		// Spin for a bit to let the other scheduler threads go idle before the
		// next round.
		doWork(Millisecond)
	}
	var total float64
	var samples float64
	maximum := Duration(0)
	for _, s := range stats {
		maximum = max(maximum, s.max)
		total += s.sum
		samples += float64(s.count)
	}
	b.ReportMetric(0, "ns/op")
	b.ReportMetric(total/samples, "avg-late-ns")
	b.ReportMetric(float64(maximum.Nanoseconds()), "max-late-ns")
}

// Benchmark timer latency with staggered wakeup times and varying CPU bound
// workloads. https://golang.org/issue/38860
func BenchmarkStaggeredTickerLatency(b *testing.B) {
	gmp := runtime.GOMAXPROCS(0)
	if gmp < 2 || runtime.NumCPU() < gmp {
		b.Skip("skipping with GOMAXPROCS < 2 or NumCPU < GOMAXPROCS")
	}

	const delay = 3 * Millisecond

	for _, dur := range []Duration{300 * Microsecond, 2 * Millisecond} {
		b.Run(fmt.Sprintf("work-dur=%s", dur), func(b *testing.B) {
			for tickersPerP := 1; tickersPerP < int(delay/dur)+1; tickersPerP++ {
				tickerCount := gmp * tickersPerP
				b.Run(fmt.Sprintf("tickers-per-P=%d", tickersPerP), func(b *testing.B) {
					// allocate memory now to avoid GC interference later.
					stats := make([]struct {
						sum   float64
						max   Duration
						count int64
						_     [5]int64 // cache line padding
					}, tickerCount)

					// Ensure the time to start new threads to service timers
					// will not pollute the results.
					warmupScheduler(gmp)

					b.ResetTimer()

					var wg sync.WaitGroup
					wg.Add(tickerCount)
					for j := 0; j < tickerCount; j++ {
						j := j
						doWork(delay / Duration(gmp))
						expectedWakeup := Now().Add(delay)
						ticker := NewTicker(delay)
						go func(c int, ticker *Ticker, firstWake Time) {
							defer ticker.Stop()

							for ; c > 0; c-- {
								<-ticker.C
								late := Since(expectedWakeup)
								if late < 0 {
									late = 0
								}
								stats[j].count++
								stats[j].sum += float64(late.Nanoseconds())
								if late > stats[j].max {
									stats[j].max = late
								}
								expectedWakeup = expectedWakeup.Add(delay)
								doWork(dur)
							}
							wg.Done()
						}(b.N, ticker, expectedWakeup)
					}
					wg.Wait()

					var total float64
					var samples float64
					max := Duration(0)
					for _, s := range stats {
						if s.max > max {
							max = s.max
						}
						total += s.sum
						samples += float64(s.count)
					}
					b.ReportMetric(0, "ns/op")
					b.ReportMetric(total/samples, "avg-late-ns")
					b.ReportMetric(float64(max.Nanoseconds()), "max-late-ns")
				})
			}
		})
	}
}

// warmupScheduler ensures the scheduler has at least targetThreadCount threads
// in its thread pool.
func warmupScheduler(targetThreadCount int) {
	var wg sync.WaitGroup
	var count int32
	for i := 0; i < targetThreadCount; i++ {
		wg.Add(1)
		go func() {
			atomic.AddInt32(&count, 1)
			for atomic.LoadInt32(&count) < int32(targetThreadCount) {
				// spin until all threads started
			}

			// spin a bit more to ensure they are all running on separate CPUs.
			doWork(Millisecond)
			wg.Done()
		}()
	}
	wg.Wait()
}

func doWork(dur Duration) {
	start := Now()
	for Since(start) < dur {
	}
}

func BenchmarkAdjustTimers10000(b *testing.B) {
	benchmark(b, func(pb *testing.PB) {
		for pb.Next() {
			const n = 10000
			timers := make([]*Timer, 0, n)
			for range n {
				t := AfterFunc(Hour, func() {})
				timers = append(timers, t)
			}
			timers[n-1].Reset(Nanosecond)
			Sleep(Microsecond)
			for _, t := range timers {
				t.Stop()
			}
		}
	})
}
