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" {
   155  		t.Skipf("skipping on %s - too slow", runtime.GOOS)
   156  	}
   157  	t.Parallel()
   158  	var wg sync.WaitGroup
   159  	defer wg.Wait()
   160  
   161  	// Build up the timer heap.
   162  	const count = 5000
   163  	wg.Add(count)
   164  	for range count {
   165  		go func() {
   166  			defer wg.Done()
   167  			Sleep(10 * Microsecond)
   168  		}()
   169  	}
   170  	for range count {
   171  		Sleep(1 * Microsecond)
   172  	}
   173  
   174  	// Give ourselves 60 seconds to complete.
   175  	// This used to reliably fail on a Mac M3 laptop,
   176  	// which needed 77 seconds.
   177  	// Trybots are slower, so it will fail even more reliably there.
   178  	// With the fix, the code runs in under a second.
   179  	done := make(chan bool)
   180  	AfterFunc(60*Second, func() { close(done) })
   181  
   182  	// Set up a queuing goroutine to ping pong through the scheduler.
   183  	inQ := make(chan func())
   184  	outQ := make(chan func())
   185  
   186  	defer close(inQ)
   187  
   188  	wg.Add(1)
   189  	go func() {
   190  		defer wg.Done()
   191  		defer close(outQ)
   192  		var q []func()
   193  		for {
   194  			var sendTo chan func()
   195  			var send func()
   196  			if len(q) > 0 {
   197  				sendTo = outQ
   198  				send = q[0]
   199  			}
   200  			select {
   201  			case sendTo <- send:
   202  				q = q[1:]
   203  			case f, ok := <-inQ:
   204  				if !ok {
   205  					return
   206  				}
   207  				q = append(q, f)
   208  			case <-done:
   209  				return
   210  			}
   211  		}
   212  	}()
   213  
   214  	for i := range 50000 {
   215  		const try = 20
   216  		for range try {
   217  			inQ <- func() {}
   218  		}
   219  		for range try {
   220  			select {
   221  			case _, ok := <-outQ:
   222  				if !ok {
   223  					t.Fatal("output channel is closed")
   224  				}
   225  			case <-After(5 * Second):
   226  				t.Fatalf("failed to read work, iteration %d", i)
   227  			case <-done:
   228  				t.Fatal("timer expired")
   229  			}
   230  		}
   231  	}
   232  }
   233  func BenchmarkTicker(b *testing.B) {
   234  	benchmark(b, func(pb *testing.PB) {
   235  		ticker := NewTicker(Nanosecond)
   236  		for pb.Next() {
   237  			<-ticker.C
   238  		}
   239  		ticker.Stop()
   240  	})
   241  }
   242  
   243  func BenchmarkTickerReset(b *testing.B) {
   244  	benchmark(b, func(pb *testing.PB) {
   245  		ticker := NewTicker(Nanosecond)
   246  		for pb.Next() {
   247  			ticker.Reset(Nanosecond * 2)
   248  		}
   249  		ticker.Stop()
   250  	})
   251  }
   252  
   253  func BenchmarkTickerResetNaive(b *testing.B) {
   254  	benchmark(b, func(pb *testing.PB) {
   255  		ticker := NewTicker(Nanosecond)
   256  		for pb.Next() {
   257  			ticker.Stop()
   258  			ticker = NewTicker(Nanosecond * 2)
   259  		}
   260  		ticker.Stop()
   261  	})
   262  }
   263  
   264  func TestTimerGC(t *testing.T) {
   265  	run := func(t *testing.T, what string, f func()) {
   266  		t.Helper()
   267  		t.Run(what, func(t *testing.T) {
   268  			t.Helper()
   269  			const N = 1e4
   270  			var stats runtime.MemStats
   271  			runtime.GC()
   272  			runtime.GC()
   273  			runtime.GC()
   274  			runtime.ReadMemStats(&stats)
   275  			before := int64(stats.Mallocs - stats.Frees)
   276  
   277  			for j := 0; j < N; j++ {
   278  				f()
   279  			}
   280  
   281  			runtime.GC()
   282  			runtime.GC()
   283  			runtime.GC()
   284  			runtime.ReadMemStats(&stats)
   285  			after := int64(stats.Mallocs - stats.Frees)
   286  
   287  			// Allow some slack, but inuse >= N means at least 1 allocation per iteration.
   288  			inuse := after - before
   289  			if inuse >= N {
   290  				t.Errorf("%s did not get GC'ed: %d allocations", what, inuse)
   291  
   292  				Sleep(1 * Second)
   293  				runtime.ReadMemStats(&stats)
   294  				after := int64(stats.Mallocs - stats.Frees)
   295  				inuse = after - before
   296  				t.Errorf("after a sleep: %d allocations", inuse)
   297  			}
   298  		})
   299  	}
   300  
   301  	run(t, "After", func() { After(Hour) })
   302  	run(t, "Tick", func() { Tick(Hour) })
   303  	run(t, "NewTimer", func() { NewTimer(Hour) })
   304  	run(t, "NewTicker", func() { NewTicker(Hour) })
   305  	run(t, "NewTimerStop", func() { NewTimer(Hour).Stop() })
   306  	run(t, "NewTickerStop", func() { NewTicker(Hour).Stop() })
   307  }
   308  
   309  func TestChan(t *testing.T) {
   310  	for _, name := range []string{"0", "1", "2"} {
   311  		t.Run("asynctimerchan="+name, func(t *testing.T) {
   312  			t.Setenv("GODEBUG", "asynctimerchan="+name)
   313  			t.Run("Timer", func(t *testing.T) {
   314  				tim := NewTimer(10000 * Second)
   315  				testTimerChan(t, tim, tim.C, name == "0")
   316  			})
   317  			t.Run("Ticker", func(t *testing.T) {
   318  				tim := &tickerTimer{Ticker: NewTicker(10000 * Second)}
   319  				testTimerChan(t, tim, tim.C, name == "0")
   320  			})
   321  		})
   322  	}
   323  }
   324  
   325  type timer interface {
   326  	Stop() bool
   327  	Reset(Duration) bool
   328  }
   329  
   330  // tickerTimer is a Timer with Reset and Stop methods that return bools,
   331  // to have the same signatures as Timer.
   332  type tickerTimer struct {
   333  	*Ticker
   334  	stopped bool
   335  }
   336  
   337  func (t *tickerTimer) Stop() bool {
   338  	pending := !t.stopped
   339  	t.stopped = true
   340  	t.Ticker.Stop()
   341  	return pending
   342  }
   343  
   344  func (t *tickerTimer) Reset(d Duration) bool {
   345  	pending := !t.stopped
   346  	t.stopped = false
   347  	t.Ticker.Reset(d)
   348  	return pending
   349  }
   350  
   351  func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) {
   352  	_, isTimer := tim.(*Timer)
   353  	isTicker := !isTimer
   354  
   355  	// Retry parameters. Enough to deflake even on slow machines.
   356  	// Windows in particular has very coarse timers so we have to
   357  	// wait 10ms just to make a timer go off.
   358  	const (
   359  		sched      = 10 * Millisecond
   360  		tries      = 100
   361  		drainTries = 5
   362  	)
   363  
   364  	// drain1 removes one potential stale time value
   365  	// from the timer/ticker channel after Reset.
   366  	// When using Go 1.23 sync timers/tickers, draining is never needed
   367  	// (that's the whole point of the sync timer/ticker change).
   368  	drain1 := func() {
   369  		for range drainTries {
   370  			select {
   371  			case <-C:
   372  				return
   373  			default:
   374  			}
   375  			Sleep(sched)
   376  		}
   377  	}
   378  
   379  	// drainAsync removes potential stale time values after Stop/Reset.
   380  	// When using Go 1 async timers, draining one or two values
   381  	// may be needed after Reset or Stop (see comments in body for details).
   382  	drainAsync := func() {
   383  		if synctimerchan {
   384  			// sync timers must have the right semantics without draining:
   385  			// there are no stale values.
   386  			return
   387  		}
   388  
   389  		// async timers can send one stale value (then the timer is disabled).
   390  		drain1()
   391  		if isTicker {
   392  			// async tickers can send two stale values: there may be one
   393  			// sitting in the channel buffer, and there may also be one
   394  			// send racing with the Reset/Stop+drain that arrives after
   395  			// the first drain1 has pulled the value out.
   396  			// This is rare, but it does happen on overloaded builder machines.
   397  			// It can also be reproduced on an M3 MacBook Pro using:
   398  			//
   399  			//	go test -c strings
   400  			//	stress ./strings.test &   # chew up CPU
   401  			//	go test -c -race time
   402  			//	stress -p 48 ./time.test -test.count=10 -test.run=TestChan/asynctimerchan=1/Ticker
   403  			drain1()
   404  		}
   405  	}
   406  	noTick := func() {
   407  		t.Helper()
   408  		select {
   409  		default:
   410  		case <-C:
   411  			t.Errorf("extra tick")
   412  		}
   413  	}
   414  	assertTick := func() {
   415  		t.Helper()
   416  		select {
   417  		default:
   418  		case <-C:
   419  			return
   420  		}
   421  		for range tries {
   422  			Sleep(sched)
   423  			select {
   424  			default:
   425  			case <-C:
   426  				return
   427  			}
   428  		}
   429  		t.Errorf("missing tick")
   430  	}
   431  	assertLen := func() {
   432  		t.Helper()
   433  		if synctimerchan {
   434  			if n := len(C); n != 0 {
   435  				t.Errorf("synctimer has len(C) = %d, want 0 (always)", n)
   436  			}
   437  			return
   438  		}
   439  		var n int
   440  		if n = len(C); n == 1 {
   441  			return
   442  		}
   443  		for range tries {
   444  			Sleep(sched)
   445  			if n = len(C); n == 1 {
   446  				return
   447  			}
   448  		}
   449  		t.Errorf("len(C) = %d, want 1", n)
   450  	}
   451  
   452  	// Test simple stop; timer never in heap.
   453  	tim.Stop()
   454  	noTick()
   455  
   456  	// Test modify of timer not in heap.
   457  	tim.Reset(10000 * Second)
   458  	noTick()
   459  
   460  	if synctimerchan {
   461  		// Test modify of timer in heap.
   462  		tim.Reset(1)
   463  		Sleep(sched)
   464  		if l, c := len(C), cap(C); l != 0 || c != 0 {
   465  			//t.Fatalf("len(C), cap(C) = %d, %d, want 0, 0", l, c)
   466  		}
   467  		assertTick()
   468  	} else {
   469  		// Test modify of timer in heap.
   470  		tim.Reset(1)
   471  		assertTick()
   472  		Sleep(sched)
   473  		tim.Reset(10000 * Second)
   474  		drainAsync()
   475  		noTick()
   476  
   477  		// Test that len sees an immediate tick arrive
   478  		// for Reset of timer in heap.
   479  		tim.Reset(1)
   480  		assertLen()
   481  		assertTick()
   482  
   483  		// Test that len sees an immediate tick arrive
   484  		// for Reset of timer NOT in heap.
   485  		tim.Stop()
   486  		drainAsync()
   487  		tim.Reset(1)
   488  		assertLen()
   489  		assertTick()
   490  	}
   491  
   492  	// Sleep long enough that a second tick must happen if this is a ticker.
   493  	// Test that Reset does not lose the tick that should have happened.
   494  	Sleep(sched)
   495  	tim.Reset(10000 * Second)
   496  	drainAsync()
   497  	noTick()
   498  
   499  	notDone := func(done chan bool) {
   500  		t.Helper()
   501  		select {
   502  		default:
   503  		case <-done:
   504  			t.Fatalf("early done")
   505  		}
   506  	}
   507  
   508  	waitDone := func(done chan bool) {
   509  		t.Helper()
   510  		for range tries {
   511  			Sleep(sched)
   512  			select {
   513  			case <-done:
   514  				return
   515  			default:
   516  			}
   517  		}
   518  		t.Fatalf("never got done")
   519  	}
   520  
   521  	// Reset timer in heap (already reset above, but just in case).
   522  	tim.Reset(10000 * Second)
   523  	drainAsync()
   524  
   525  	// Test stop while timer in heap (because goroutine is blocked on <-C).
   526  	done := make(chan bool)
   527  	notDone(done)
   528  	go func() {
   529  		<-C
   530  		close(done)
   531  	}()
   532  	Sleep(sched)
   533  	notDone(done)
   534  
   535  	// Test reset far away while timer in heap.
   536  	tim.Reset(20000 * Second)
   537  	Sleep(sched)
   538  	notDone(done)
   539  
   540  	// Test imminent reset while in heap.
   541  	tim.Reset(1)
   542  	waitDone(done)
   543  
   544  	// If this is a ticker, another tick should have come in already
   545  	// (they are 1ns apart). If a timer, it should have stopped.
   546  	if isTicker {
   547  		assertTick()
   548  	} else {
   549  		noTick()
   550  	}
   551  
   552  	tim.Stop()
   553  	drainAsync()
   554  	noTick()
   555  
   556  	// Again using select and with two goroutines waiting.
   557  	tim.Reset(10000 * Second)
   558  	drainAsync()
   559  	done = make(chan bool, 2)
   560  	done1 := make(chan bool)
   561  	done2 := make(chan bool)
   562  	stop := make(chan bool)
   563  	go func() {
   564  		select {
   565  		case <-C:
   566  			done <- true
   567  		case <-stop:
   568  		}
   569  		close(done1)
   570  	}()
   571  	go func() {
   572  		select {
   573  		case <-C:
   574  			done <- true
   575  		case <-stop:
   576  		}
   577  		close(done2)
   578  	}()
   579  	Sleep(sched)
   580  	notDone(done)
   581  	tim.Reset(sched / 2)
   582  	Sleep(sched)
   583  	waitDone(done)
   584  	tim.Stop()
   585  	close(stop)
   586  	waitDone(done1)
   587  	waitDone(done2)
   588  	if isTicker {
   589  		// extra send might have sent done again
   590  		// (handled by buffering done above).
   591  		select {
   592  		default:
   593  		case <-done:
   594  		}
   595  		// extra send after that might have filled C.
   596  		select {
   597  		default:
   598  		case <-C:
   599  		}
   600  	}
   601  	notDone(done)
   602  
   603  	// Test enqueueTimerChan when timer is stopped.
   604  	stop = make(chan bool)
   605  	done = make(chan bool, 2)
   606  	for range 2 {
   607  		go func() {
   608  			select {
   609  			case <-C:
   610  				panic("unexpected data")
   611  			case <-stop:
   612  			}
   613  			done <- true
   614  		}()
   615  	}
   616  	Sleep(sched)
   617  	close(stop)
   618  	waitDone(done)
   619  	waitDone(done)
   620  
   621  	// Test that Stop and Reset block old values from being received.
   622  	// (Proposal go.dev/issue/37196.)
   623  	if synctimerchan {
   624  		tim.Reset(1)
   625  		Sleep(10 * Millisecond)
   626  		if pending := tim.Stop(); pending != true {
   627  			t.Errorf("tim.Stop() = %v, want true", pending)
   628  		}
   629  		noTick()
   630  
   631  		tim.Reset(Hour)
   632  		noTick()
   633  		if pending := tim.Reset(1); pending != true {
   634  			t.Errorf("tim.Stop() = %v, want true", pending)
   635  		}
   636  		assertTick()
   637  		Sleep(10 * Millisecond)
   638  		if isTicker {
   639  			assertTick()
   640  			Sleep(10 * Millisecond)
   641  		} else {
   642  			noTick()
   643  		}
   644  		if pending, want := tim.Reset(Hour), isTicker; pending != want {
   645  			t.Errorf("tim.Stop() = %v, want %v", pending, want)
   646  		}
   647  		noTick()
   648  	}
   649  }
   650  
   651  func TestManualTicker(t *testing.T) {
   652  	// Code should not do this, but some old code dating to Go 1.9 does.
   653  	// Make sure this doesn't crash.
   654  	// See go.dev/issue/21874.
   655  	c := make(chan Time)
   656  	tick := &Ticker{C: c}
   657  	tick.Stop()
   658  }
   659  
   660  func TestAfterTimes(t *testing.T) {
   661  	t.Parallel()
   662  	// Using After(10ms) but waiting for 500ms to read the channel
   663  	// should produce a time from start+10ms, not start+500ms.
   664  	// Make sure it does.
   665  	// To avoid flakes due to very long scheduling delays,
   666  	// require 10 failures in a row before deciding something is wrong.
   667  	for range 10 {
   668  		start := Now()
   669  		c := After(10 * Millisecond)
   670  		Sleep(500 * Millisecond)
   671  		dt := (<-c).Sub(start)
   672  		if dt < 400*Millisecond {
   673  			return
   674  		}
   675  		t.Logf("After(10ms) time is +%v, want <400ms", dt)
   676  	}
   677  	t.Errorf("not working")
   678  }
   679  
   680  func TestTickTimes(t *testing.T) {
   681  	t.Parallel()
   682  	// See comment in TestAfterTimes
   683  	for range 10 {
   684  		start := Now()
   685  		c := Tick(10 * Millisecond)
   686  		Sleep(500 * Millisecond)
   687  		dt := (<-c).Sub(start)
   688  		if dt < 400*Millisecond {
   689  			return
   690  		}
   691  		t.Logf("Tick(10ms) time is +%v, want <400ms", dt)
   692  	}
   693  	t.Errorf("not working")
   694  }
   695  

View as plain text