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 5package time_test 6 7import ( 8 "fmt" 9 "runtime" 10 "sync" 11 "testing" 12 . "time" 13) 14 15func 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 103func 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. 110func 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. 125func 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. 133func 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. 143func 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 153func 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} 233func 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 243func 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 253func 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 264func 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 309func 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 325type 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. 332type tickerTimer struct { 333 *Ticker 334 stopped bool 335} 336 337func (t *tickerTimer) Stop() bool { 338 pending := !t.stopped 339 t.stopped = true 340 t.Ticker.Stop() 341 return pending 342} 343 344func (t *tickerTimer) Reset(d Duration) bool { 345 pending := !t.stopped 346 t.stopped = false 347 t.Ticker.Reset(d) 348 return pending 349} 350 351func 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 651func 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 660func 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 680func 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