Source file src/time/tick_test.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package time_test
     6  
     7  import (
     8  	"fmt"
     9  	"runtime"
    10  	"sync"
    11  	"testing"
    12  	. "time"
    13  )
    14  
    15  func TestTicker(t *testing.T) {
    16  	t.Parallel()
    17  
    18  	// We want to test that a ticker takes as much time as expected.
    19  	// Since we don't want the test to run for too long, we don't
    20  	// want to use lengthy times. This makes the test inherently flaky.
    21  	// Start with a short time, but try again with a long one if the
    22  	// first test fails.
    23  
    24  	baseCount := 10
    25  	baseDelta := 20 * Millisecond
    26  
    27  	// On Darwin ARM64 the tick frequency seems limited. Issue 35692.
    28  	if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" {
    29  		// The following test will run ticker count/2 times then reset
    30  		// the ticker to double the duration for the rest of count/2.
    31  		// Since tick frequency is limited on Darwin ARM64, use even
    32  		// number to give the ticks more time to let the test pass.
    33  		// See CL 220638.
    34  		baseCount = 6
    35  		baseDelta = 100 * Millisecond
    36  	}
    37  
    38  	var errs []string
    39  	logErrs := func() {
    40  		for _, e := range errs {
    41  			t.Log(e)
    42  		}
    43  	}
    44  
    45  	for _, test := range []struct {
    46  		count int
    47  		delta Duration
    48  	}{{
    49  		count: baseCount,
    50  		delta: baseDelta,
    51  	}, {
    52  		count: 8,
    53  		delta: 1 * Second,
    54  	}} {
    55  		count, delta := test.count, test.delta
    56  		ticker := NewTicker(delta)
    57  		t0 := Now()
    58  		for range count / 2 {
    59  			<-ticker.C
    60  		}
    61  		ticker.Reset(delta * 2)
    62  		for range count - count/2 {
    63  			<-ticker.C
    64  		}
    65  		ticker.Stop()
    66  		t1 := Now()
    67  		dt := t1.Sub(t0)
    68  		target := 3 * delta * Duration(count/2)
    69  		slop := target * 3 / 10
    70  		if dt < target-slop || dt > target+slop {
    71  			errs = append(errs, fmt.Sprintf("%d %s ticks then %d %s ticks took %s, expected [%s,%s]", count/2, delta, count/2, delta*2, dt, target-slop, target+slop))
    72  			if dt > target+slop {
    73  				// System may be overloaded; sleep a bit
    74  				// in the hopes it will recover.
    75  				Sleep(Second / 2)
    76  			}
    77  			continue
    78  		}
    79  		// Now test that the ticker stopped.
    80  		Sleep(2 * delta)
    81  		select {
    82  		case <-ticker.C:
    83  			errs = append(errs, "Ticker did not shut down")
    84  			continue
    85  		default:
    86  			// ok
    87  		}
    88  
    89  		// Test passed, so all done.
    90  		if len(errs) > 0 {
    91  			t.Logf("saw %d errors, ignoring to avoid flakiness", len(errs))
    92  			logErrs()
    93  		}
    94  
    95  		return
    96  	}
    97  
    98  	t.Errorf("saw %d errors", len(errs))
    99  	logErrs()
   100  }
   101  
   102  // Issue 21874
   103  func TestTickerStopWithDirectInitialization(t *testing.T) {
   104  	c := make(chan Time)
   105  	tk := &Ticker{C: c}
   106  	tk.Stop()
   107  }
   108  
   109  // Test that a bug tearing down a ticker has been fixed. This routine should not deadlock.
   110  func TestTeardown(t *testing.T) {
   111  	t.Parallel()
   112  
   113  	Delta := 100 * Millisecond
   114  	if testing.Short() {
   115  		Delta = 20 * Millisecond
   116  	}
   117  	for range 3 {
   118  		ticker := NewTicker(Delta)
   119  		<-ticker.C
   120  		ticker.Stop()
   121  	}
   122  }
   123  
   124  // Test the Tick convenience wrapper.
   125  func TestTick(t *testing.T) {
   126  	// Test that giving a negative duration returns nil.
   127  	if got := Tick(-1); got != nil {
   128  		t.Errorf("Tick(-1) = %v; want nil", got)
   129  	}
   130  }
   131  
   132  // Test that NewTicker panics when given a duration less than zero.
   133  func TestNewTickerLtZeroDuration(t *testing.T) {
   134  	defer func() {
   135  		if err := recover(); err == nil {
   136  			t.Errorf("NewTicker(-1) should have panicked")
   137  		}
   138  	}()
   139  	NewTicker(-1)
   140  }
   141  
   142  // Test that Ticker.Reset panics when given a duration less than zero.
   143  func TestTickerResetLtZeroDuration(t *testing.T) {
   144  	defer func() {
   145  		if err := recover(); err == nil {
   146  			t.Errorf("Ticker.Reset(0) should have panicked")
   147  		}
   148  	}()
   149  	tk := NewTicker(Second)
   150  	tk.Reset(0)
   151  }
   152  
   153  func TestLongAdjustTimers(t *testing.T) {
   154  	if runtime.GOOS == "android" || runtime.GOOS == "ios" || runtime.GOOS == "plan9" {
   155  		t.Skipf("skipping on %s - too slow", runtime.GOOS)
   156  	}
   157  	if testing.Short() && runtime.NumCPU() < 2 {
   158  		t.Skipf("skipping in short mode, insufficient CPUs")
   159  	}
   160  
   161  	t.Parallel()
   162  	var wg sync.WaitGroup
   163  	defer wg.Wait()
   164  
   165  	// Build up the timer heap.
   166  	const count = 5000
   167  	wg.Add(count)
   168  	for range count {
   169  		go func() {
   170  			defer wg.Done()
   171  			Sleep(10 * Microsecond)
   172  		}()
   173  	}
   174  	for range count {
   175  		Sleep(1 * Microsecond)
   176  	}
   177  
   178  	// Give ourselves 60 seconds to complete.
   179  	// This used to reliably fail on a Mac M3 laptop,
   180  	// which needed 77 seconds.
   181  	// Trybots are slower, so it will fail even more reliably there.
   182  	// With the fix, the code runs in under a second.
   183  	done := make(chan bool)
   184  	AfterFunc(60*Second, func() { close(done) })
   185  
   186  	// Set up a queuing goroutine to ping pong through the scheduler.
   187  	inQ := make(chan func())
   188  	outQ := make(chan func())
   189  
   190  	defer close(inQ)
   191  
   192  	wg.Add(1)
   193  	go func() {
   194  		defer wg.Done()
   195  		defer close(outQ)
   196  		var q []func()
   197  		for {
   198  			var sendTo chan func()
   199  			var send func()
   200  			if len(q) > 0 {
   201  				sendTo = outQ
   202  				send = q[0]
   203  			}
   204  			select {
   205  			case sendTo <- send:
   206  				q = q[1:]
   207  			case f, ok := <-inQ:
   208  				if !ok {
   209  					return
   210  				}
   211  				q = append(q, f)
   212  			case <-done:
   213  				return
   214  			}
   215  		}
   216  	}()
   217  
   218  	for i := range 50000 {
   219  		const try = 20
   220  		for range try {
   221  			inQ <- func() {}
   222  		}
   223  		for range try {
   224  			select {
   225  			case _, ok := <-outQ:
   226  				if !ok {
   227  					t.Fatal("output channel is closed")
   228  				}
   229  			case <-After(5 * Second):
   230  				t.Fatalf("failed to read work, iteration %d", i)
   231  			case <-done:
   232  				t.Fatal("timer expired")
   233  			}
   234  		}
   235  	}
   236  }
   237  func BenchmarkTicker(b *testing.B) {
   238  	benchmark(b, func(pb *testing.PB) {
   239  		ticker := NewTicker(Nanosecond)
   240  		for pb.Next() {
   241  			<-ticker.C
   242  		}
   243  		ticker.Stop()
   244  	})
   245  }
   246  
   247  func BenchmarkTickerReset(b *testing.B) {
   248  	benchmark(b, func(pb *testing.PB) {
   249  		ticker := NewTicker(Nanosecond)
   250  		for pb.Next() {
   251  			ticker.Reset(Nanosecond * 2)
   252  		}
   253  		ticker.Stop()
   254  	})
   255  }
   256  
   257  func BenchmarkTickerResetNaive(b *testing.B) {
   258  	benchmark(b, func(pb *testing.PB) {
   259  		ticker := NewTicker(Nanosecond)
   260  		for pb.Next() {
   261  			ticker.Stop()
   262  			ticker = NewTicker(Nanosecond * 2)
   263  		}
   264  		ticker.Stop()
   265  	})
   266  }
   267  
   268  func TestTimerGC(t *testing.T) {
   269  	run := func(t *testing.T, what string, f func()) {
   270  		t.Helper()
   271  		t.Run(what, func(t *testing.T) {
   272  			t.Helper()
   273  			const N = 1e4
   274  			var stats runtime.MemStats
   275  			runtime.GC()
   276  			runtime.GC()
   277  			runtime.GC()
   278  			runtime.ReadMemStats(&stats)
   279  			before := int64(stats.Mallocs - stats.Frees)
   280  
   281  			for j := 0; j < N; j++ {
   282  				f()
   283  			}
   284  
   285  			runtime.GC()
   286  			runtime.GC()
   287  			runtime.GC()
   288  			runtime.ReadMemStats(&stats)
   289  			after := int64(stats.Mallocs - stats.Frees)
   290  
   291  			// Allow some slack, but inuse >= N means at least 1 allocation per iteration.
   292  			inuse := after - before
   293  			if inuse >= N {
   294  				t.Errorf("%s did not get GC'ed: %d allocations", what, inuse)
   295  
   296  				Sleep(1 * Second)
   297  				runtime.ReadMemStats(&stats)
   298  				after := int64(stats.Mallocs - stats.Frees)
   299  				inuse = after - before
   300  				t.Errorf("after a sleep: %d allocations", inuse)
   301  			}
   302  		})
   303  	}
   304  
   305  	run(t, "After", func() { After(Hour) })
   306  	run(t, "Tick", func() { Tick(Hour) })
   307  	run(t, "NewTimer", func() { NewTimer(Hour) })
   308  	run(t, "NewTicker", func() { NewTicker(Hour) })
   309  	run(t, "NewTimerStop", func() { NewTimer(Hour).Stop() })
   310  	run(t, "NewTickerStop", func() { NewTicker(Hour).Stop() })
   311  }
   312  
   313  func TestChan(t *testing.T) {
   314  	for _, name := range []string{"0", "1", "2"} {
   315  		t.Run("asynctimerchan="+name, func(t *testing.T) {
   316  			t.Setenv("GODEBUG", "asynctimerchan="+name)
   317  			t.Run("Timer", func(t *testing.T) {
   318  				tim := NewTimer(10000 * Second)
   319  				testTimerChan(t, tim, tim.C, name == "0")
   320  			})
   321  			t.Run("Ticker", func(t *testing.T) {
   322  				tim := &tickerTimer{Ticker: NewTicker(10000 * Second)}
   323  				testTimerChan(t, tim, tim.C, name == "0")
   324  			})
   325  		})
   326  	}
   327  }
   328  
   329  type timer interface {
   330  	Stop() bool
   331  	Reset(Duration) bool
   332  }
   333  
   334  // tickerTimer is a Timer with Reset and Stop methods that return bools,
   335  // to have the same signatures as Timer.
   336  type tickerTimer struct {
   337  	*Ticker
   338  	stopped bool
   339  }
   340  
   341  func (t *tickerTimer) Stop() bool {
   342  	pending := !t.stopped
   343  	t.stopped = true
   344  	t.Ticker.Stop()
   345  	return pending
   346  }
   347  
   348  func (t *tickerTimer) Reset(d Duration) bool {
   349  	pending := !t.stopped
   350  	t.stopped = false
   351  	t.Ticker.Reset(d)
   352  	return pending
   353  }
   354  
   355  func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) {
   356  	_, isTimer := tim.(*Timer)
   357  	isTicker := !isTimer
   358  
   359  	// Retry parameters. Enough to deflake even on slow machines.
   360  	// Windows in particular has very coarse timers so we have to
   361  	// wait 10ms just to make a timer go off.
   362  	const (
   363  		sched      = 10 * Millisecond
   364  		tries      = 100
   365  		drainTries = 5
   366  	)
   367  
   368  	// drain1 removes one potential stale time value
   369  	// from the timer/ticker channel after Reset.
   370  	// When using Go 1.23 sync timers/tickers, draining is never needed
   371  	// (that's the whole point of the sync timer/ticker change).
   372  	drain1 := func() {
   373  		for range drainTries {
   374  			select {
   375  			case <-C:
   376  				return
   377  			default:
   378  			}
   379  			Sleep(sched)
   380  		}
   381  	}
   382  
   383  	// drainAsync removes potential stale time values after Stop/Reset.
   384  	// When using Go 1 async timers, draining one or two values
   385  	// may be needed after Reset or Stop (see comments in body for details).
   386  	drainAsync := func() {
   387  		if synctimerchan {
   388  			// sync timers must have the right semantics without draining:
   389  			// there are no stale values.
   390  			return
   391  		}
   392  
   393  		// async timers can send one stale value (then the timer is disabled).
   394  		drain1()
   395  		if isTicker {
   396  			// async tickers can send two stale values: there may be one
   397  			// sitting in the channel buffer, and there may also be one
   398  			// send racing with the Reset/Stop+drain that arrives after
   399  			// the first drain1 has pulled the value out.
   400  			// This is rare, but it does happen on overloaded builder machines.
   401  			// It can also be reproduced on an M3 MacBook Pro using:
   402  			//
   403  			//	go test -c strings
   404  			//	stress ./strings.test &   # chew up CPU
   405  			//	go test -c -race time
   406  			//	stress -p 48 ./time.test -test.count=10 -test.run=TestChan/asynctimerchan=1/Ticker
   407  			drain1()
   408  		}
   409  	}
   410  	noTick := func() {
   411  		t.Helper()
   412  		select {
   413  		default:
   414  		case <-C:
   415  			t.Errorf("extra tick")
   416  		}
   417  	}
   418  	assertTick := func() {
   419  		t.Helper()
   420  		select {
   421  		default:
   422  		case <-C:
   423  			return
   424  		}
   425  		for range tries {
   426  			Sleep(sched)
   427  			select {
   428  			default:
   429  			case <-C:
   430  				return
   431  			}
   432  		}
   433  		t.Errorf("missing tick")
   434  	}
   435  	assertLen := func() {
   436  		t.Helper()
   437  		if synctimerchan {
   438  			if n := len(C); n != 0 {
   439  				t.Errorf("synctimer has len(C) = %d, want 0 (always)", n)
   440  			}
   441  			return
   442  		}
   443  		var n int
   444  		if n = len(C); n == 1 {
   445  			return
   446  		}
   447  		for range tries {
   448  			Sleep(sched)
   449  			if n = len(C); n == 1 {
   450  				return
   451  			}
   452  		}
   453  		t.Errorf("len(C) = %d, want 1", n)
   454  	}
   455  
   456  	// Test simple stop; timer never in heap.
   457  	tim.Stop()
   458  	noTick()
   459  
   460  	// Test modify of timer not in heap.
   461  	tim.Reset(10000 * Second)
   462  	noTick()
   463  
   464  	if synctimerchan {
   465  		// Test modify of timer in heap.
   466  		tim.Reset(1)
   467  		Sleep(sched)
   468  		if l, c := len(C), cap(C); l != 0 || c != 0 {
   469  			// t.Fatalf("len(C), cap(C) = %d, %d, want 0, 0", l, c)
   470  		}
   471  		assertTick()
   472  	} else {
   473  		// Test modify of timer in heap.
   474  		tim.Reset(1)
   475  		assertTick()
   476  		Sleep(sched)
   477  		tim.Reset(10000 * Second)
   478  		drainAsync()
   479  		noTick()
   480  
   481  		// Test that len sees an immediate tick arrive
   482  		// for Reset of timer in heap.
   483  		tim.Reset(1)
   484  		assertLen()
   485  		assertTick()
   486  
   487  		// Test that len sees an immediate tick arrive
   488  		// for Reset of timer NOT in heap.
   489  		tim.Stop()
   490  		drainAsync()
   491  		tim.Reset(1)
   492  		assertLen()
   493  		assertTick()
   494  	}
   495  
   496  	// Sleep long enough that a second tick must happen if this is a ticker.
   497  	// Test that Reset does not lose the tick that should have happened.
   498  	Sleep(sched)
   499  	tim.Reset(10000 * Second)
   500  	drainAsync()
   501  	noTick()
   502  
   503  	notDone := func(done chan bool) {
   504  		t.Helper()
   505  		select {
   506  		default:
   507  		case <-done:
   508  			t.Fatalf("early done")
   509  		}
   510  	}
   511  
   512  	waitDone := func(done chan bool) {
   513  		t.Helper()
   514  		for range tries {
   515  			Sleep(sched)
   516  			select {
   517  			case <-done:
   518  				return
   519  			default:
   520  			}
   521  		}
   522  		t.Fatalf("never got done")
   523  	}
   524  
   525  	// Reset timer in heap (already reset above, but just in case).
   526  	tim.Reset(10000 * Second)
   527  	drainAsync()
   528  
   529  	// Test stop while timer in heap (because goroutine is blocked on <-C).
   530  	done := make(chan bool)
   531  	notDone(done)
   532  	go func() {
   533  		<-C
   534  		close(done)
   535  	}()
   536  	Sleep(sched)
   537  	notDone(done)
   538  
   539  	// Test reset far away while timer in heap.
   540  	tim.Reset(20000 * Second)
   541  	Sleep(sched)
   542  	notDone(done)
   543  
   544  	// Test imminent reset while in heap.
   545  	tim.Reset(1)
   546  	waitDone(done)
   547  
   548  	// If this is a ticker, another tick should have come in already
   549  	// (they are 1ns apart). If a timer, it should have stopped.
   550  	if isTicker {
   551  		assertTick()
   552  	} else {
   553  		noTick()
   554  	}
   555  
   556  	tim.Stop()
   557  	drainAsync()
   558  	noTick()
   559  
   560  	// Again using select and with two goroutines waiting.
   561  	tim.Reset(10000 * Second)
   562  	drainAsync()
   563  	done = make(chan bool, 2)
   564  	done1 := make(chan bool)
   565  	done2 := make(chan bool)
   566  	stop := make(chan bool)
   567  	go func() {
   568  		select {
   569  		case <-C:
   570  			done <- true
   571  		case <-stop:
   572  		}
   573  		close(done1)
   574  	}()
   575  	go func() {
   576  		select {
   577  		case <-C:
   578  			done <- true
   579  		case <-stop:
   580  		}
   581  		close(done2)
   582  	}()
   583  	Sleep(sched)
   584  	notDone(done)
   585  	tim.Reset(sched / 2)
   586  	Sleep(sched)
   587  	waitDone(done)
   588  	tim.Stop()
   589  	close(stop)
   590  	waitDone(done1)
   591  	waitDone(done2)
   592  	if isTicker {
   593  		// extra send might have sent done again
   594  		// (handled by buffering done above).
   595  		select {
   596  		default:
   597  		case <-done:
   598  		}
   599  		// extra send after that might have filled C.
   600  		select {
   601  		default:
   602  		case <-C:
   603  		}
   604  	}
   605  	notDone(done)
   606  
   607  	// Test enqueueTimerChan when timer is stopped.
   608  	stop = make(chan bool)
   609  	done = make(chan bool, 2)
   610  	for range 2 {
   611  		go func() {
   612  			select {
   613  			case <-C:
   614  				panic("unexpected data")
   615  			case <-stop:
   616  			}
   617  			done <- true
   618  		}()
   619  	}
   620  	Sleep(sched)
   621  	close(stop)
   622  	waitDone(done)
   623  	waitDone(done)
   624  
   625  	// Test that Stop and Reset block old values from being received.
   626  	// (Proposal go.dev/issue/37196.)
   627  	if synctimerchan {
   628  		tim.Reset(1)
   629  		Sleep(10 * Millisecond)
   630  		if pending := tim.Stop(); pending != true {
   631  			t.Errorf("tim.Stop() = %v, want true", pending)
   632  		}
   633  		noTick()
   634  
   635  		tim.Reset(Hour)
   636  		noTick()
   637  		if pending := tim.Reset(1); pending != true {
   638  			t.Errorf("tim.Stop() = %v, want true", pending)
   639  		}
   640  		assertTick()
   641  		Sleep(10 * Millisecond)
   642  		if isTicker {
   643  			assertTick()
   644  			Sleep(10 * Millisecond)
   645  		} else {
   646  			noTick()
   647  		}
   648  		if pending, want := tim.Reset(Hour), isTicker; pending != want {
   649  			t.Errorf("tim.Stop() = %v, want %v", pending, want)
   650  		}
   651  		noTick()
   652  	}
   653  }
   654  
   655  func TestManualTicker(t *testing.T) {
   656  	// Code should not do this, but some old code dating to Go 1.9 does.
   657  	// Make sure this doesn't crash.
   658  	// See go.dev/issue/21874.
   659  	c := make(chan Time)
   660  	tick := &Ticker{C: c}
   661  	tick.Stop()
   662  }
   663  
   664  func TestAfterTimes(t *testing.T) {
   665  	t.Parallel()
   666  	// Using After(10ms) but waiting for 500ms to read the channel
   667  	// should produce a time from start+10ms, not start+500ms.
   668  	// Make sure it does.
   669  	// To avoid flakes due to very long scheduling delays,
   670  	// require 10 failures in a row before deciding something is wrong.
   671  	for range 10 {
   672  		start := Now()
   673  		c := After(10 * Millisecond)
   674  		Sleep(500 * Millisecond)
   675  		dt := (<-c).Sub(start)
   676  		if dt < 400*Millisecond {
   677  			return
   678  		}
   679  		t.Logf("After(10ms) time is +%v, want <400ms", dt)
   680  	}
   681  	t.Errorf("not working")
   682  }
   683  
   684  func TestTickTimes(t *testing.T) {
   685  	t.Parallel()
   686  	// See comment in TestAfterTimes
   687  	for range 10 {
   688  		start := Now()
   689  		c := Tick(10 * Millisecond)
   690  		Sleep(500 * Millisecond)
   691  		dt := (<-c).Sub(start)
   692  		if dt < 400*Millisecond {
   693  			return
   694  		}
   695  		t.Logf("Tick(10ms) time is +%v, want <400ms", dt)
   696  	}
   697  	t.Errorf("not working")
   698  }
   699  

View as plain text