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 queing 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  	drain := func() {
   365  		for range drainTries {
   366  			select {
   367  			case <-C:
   368  				return
   369  			default:
   370  			}
   371  			Sleep(sched)
   372  		}
   373  	}
   374  	noTick := func() {
   375  		t.Helper()
   376  		select {
   377  		default:
   378  		case <-C:
   379  			t.Errorf("extra tick")
   380  		}
   381  	}
   382  	assertTick := func() {
   383  		t.Helper()
   384  		select {
   385  		default:
   386  		case <-C:
   387  			return
   388  		}
   389  		for range tries {
   390  			Sleep(sched)
   391  			select {
   392  			default:
   393  			case <-C:
   394  				return
   395  			}
   396  		}
   397  		t.Errorf("missing tick")
   398  	}
   399  	assertLen := func() {
   400  		t.Helper()
   401  		if synctimerchan {
   402  			if n := len(C); n != 0 {
   403  				t.Errorf("synctimer has len(C) = %d, want 0 (always)", n)
   404  			}
   405  			return
   406  		}
   407  		var n int
   408  		if n = len(C); n == 1 {
   409  			return
   410  		}
   411  		for range tries {
   412  			Sleep(sched)
   413  			if n = len(C); n == 1 {
   414  				return
   415  			}
   416  		}
   417  		t.Errorf("len(C) = %d, want 1", n)
   418  	}
   419  
   420  	// Test simple stop; timer never in heap.
   421  	tim.Stop()
   422  	noTick()
   423  
   424  	// Test modify of timer not in heap.
   425  	tim.Reset(10000 * Second)
   426  	noTick()
   427  
   428  	if synctimerchan {
   429  		// Test modify of timer in heap.
   430  		tim.Reset(1)
   431  		Sleep(sched)
   432  		if l, c := len(C), cap(C); l != 0 || c != 0 {
   433  			//t.Fatalf("len(C), cap(C) = %d, %d, want 0, 0", l, c)
   434  		}
   435  		assertTick()
   436  	} else {
   437  		// Test modify of timer in heap.
   438  		tim.Reset(1)
   439  		assertTick()
   440  		Sleep(sched)
   441  		tim.Reset(10000 * Second)
   442  		if isTicker {
   443  			assertTick()
   444  		}
   445  		noTick()
   446  
   447  		// Test that len sees an immediate tick arrive
   448  		// for Reset of timer in heap.
   449  		tim.Reset(1)
   450  		assertLen()
   451  		assertTick()
   452  
   453  		// Test that len sees an immediate tick arrive
   454  		// for Reset of timer NOT in heap.
   455  		tim.Stop()
   456  		if !synctimerchan {
   457  			drain()
   458  		}
   459  		tim.Reset(1)
   460  		assertLen()
   461  		assertTick()
   462  	}
   463  
   464  	// Sleep long enough that a second tick must happen if this is a ticker.
   465  	// Test that Reset does not lose the tick that should have happened.
   466  	Sleep(sched)
   467  	tim.Reset(10000 * Second)
   468  	if !synctimerchan && isTicker {
   469  		assertLen()
   470  		assertTick()
   471  	}
   472  	noTick()
   473  
   474  	notDone := func(done chan bool) {
   475  		t.Helper()
   476  		select {
   477  		default:
   478  		case <-done:
   479  			t.Fatalf("early done")
   480  		}
   481  	}
   482  
   483  	waitDone := func(done chan bool) {
   484  		t.Helper()
   485  		for range tries {
   486  			Sleep(sched)
   487  			select {
   488  			case <-done:
   489  				return
   490  			default:
   491  			}
   492  		}
   493  		t.Fatalf("never got done")
   494  	}
   495  
   496  	// Reset timer in heap (already reset above, but just in case).
   497  	tim.Reset(10000 * Second)
   498  	if !synctimerchan {
   499  		drain()
   500  	}
   501  
   502  	// Test stop while timer in heap (because goroutine is blocked on <-C).
   503  	done := make(chan bool)
   504  	notDone(done)
   505  	go func() {
   506  		<-C
   507  		close(done)
   508  	}()
   509  	Sleep(sched)
   510  	notDone(done)
   511  
   512  	// Test reset far away while timer in heap.
   513  	tim.Reset(20000 * Second)
   514  	Sleep(sched)
   515  	notDone(done)
   516  
   517  	// Test imminent reset while in heap.
   518  	tim.Reset(1)
   519  	waitDone(done)
   520  
   521  	// If this is a ticker, another tick should have come in already
   522  	// (they are 1ns apart). If a timer, it should have stopped.
   523  	if isTicker {
   524  		assertTick()
   525  	} else {
   526  		noTick()
   527  	}
   528  
   529  	tim.Stop()
   530  	if isTicker || !synctimerchan {
   531  		t.Logf("drain")
   532  		drain()
   533  	}
   534  	noTick()
   535  
   536  	// Again using select and with two goroutines waiting.
   537  	tim.Reset(10000 * Second)
   538  	if !synctimerchan {
   539  		drain()
   540  	}
   541  	done = make(chan bool, 2)
   542  	done1 := make(chan bool)
   543  	done2 := make(chan bool)
   544  	stop := make(chan bool)
   545  	go func() {
   546  		select {
   547  		case <-C:
   548  			done <- true
   549  		case <-stop:
   550  		}
   551  		close(done1)
   552  	}()
   553  	go func() {
   554  		select {
   555  		case <-C:
   556  			done <- true
   557  		case <-stop:
   558  		}
   559  		close(done2)
   560  	}()
   561  	Sleep(sched)
   562  	notDone(done)
   563  	tim.Reset(sched / 2)
   564  	Sleep(sched)
   565  	waitDone(done)
   566  	tim.Stop()
   567  	close(stop)
   568  	waitDone(done1)
   569  	waitDone(done2)
   570  	if isTicker {
   571  		// extra send might have sent done again
   572  		// (handled by buffering done above).
   573  		select {
   574  		default:
   575  		case <-done:
   576  		}
   577  		// extra send after that might have filled C.
   578  		select {
   579  		default:
   580  		case <-C:
   581  		}
   582  	}
   583  	notDone(done)
   584  
   585  	// Test enqueueTimerChan when timer is stopped.
   586  	stop = make(chan bool)
   587  	done = make(chan bool, 2)
   588  	for range 2 {
   589  		go func() {
   590  			select {
   591  			case <-C:
   592  				panic("unexpected data")
   593  			case <-stop:
   594  			}
   595  			done <- true
   596  		}()
   597  	}
   598  	Sleep(sched)
   599  	close(stop)
   600  	waitDone(done)
   601  	waitDone(done)
   602  
   603  	// Test that Stop and Reset block old values from being received.
   604  	// (Proposal go.dev/issue/37196.)
   605  	if synctimerchan {
   606  		tim.Reset(1)
   607  		Sleep(10 * Millisecond)
   608  		if pending := tim.Stop(); pending != true {
   609  			t.Errorf("tim.Stop() = %v, want true", pending)
   610  		}
   611  		noTick()
   612  
   613  		tim.Reset(Hour)
   614  		noTick()
   615  		if pending := tim.Reset(1); pending != true {
   616  			t.Errorf("tim.Stop() = %v, want true", pending)
   617  		}
   618  		assertTick()
   619  		Sleep(10 * Millisecond)
   620  		if isTicker {
   621  			assertTick()
   622  			Sleep(10 * Millisecond)
   623  		} else {
   624  			noTick()
   625  		}
   626  		if pending, want := tim.Reset(Hour), isTicker; pending != want {
   627  			t.Errorf("tim.Stop() = %v, want %v", pending, want)
   628  		}
   629  		noTick()
   630  	}
   631  }
   632  
   633  func TestManualTicker(t *testing.T) {
   634  	// Code should not do this, but some old code dating to Go 1.9 does.
   635  	// Make sure this doesn't crash.
   636  	// See go.dev/issue/21874.
   637  	c := make(chan Time)
   638  	tick := &Ticker{C: c}
   639  	tick.Stop()
   640  }
   641  
   642  func TestAfterTimes(t *testing.T) {
   643  	t.Parallel()
   644  	// Using After(10ms) but waiting for 500ms to read the channel
   645  	// should produce a time from start+10ms, not start+500ms.
   646  	// Make sure it does.
   647  	// To avoid flakes due to very long scheduling delays,
   648  	// require 10 failures in a row before deciding something is wrong.
   649  	for range 10 {
   650  		start := Now()
   651  		c := After(10 * Millisecond)
   652  		Sleep(500 * Millisecond)
   653  		dt := (<-c).Sub(start)
   654  		if dt < 400*Millisecond {
   655  			return
   656  		}
   657  		t.Logf("After(10ms) time is +%v, want <400ms", dt)
   658  	}
   659  	t.Errorf("not working")
   660  }
   661  
   662  func TestTickTimes(t *testing.T) {
   663  	t.Parallel()
   664  	// See comment in TestAfterTimes
   665  	for range 10 {
   666  		start := Now()
   667  		c := Tick(10 * Millisecond)
   668  		Sleep(500 * Millisecond)
   669  		dt := (<-c).Sub(start)
   670  		if dt < 400*Millisecond {
   671  			return
   672  		}
   673  		t.Logf("Tick(10ms) time is +%v, want <400ms", dt)
   674  	}
   675  	t.Errorf("not working")
   676  }
   677  

View as plain text