Source file
src/time/tick_test.go
1
2
3
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
19
20
21
22
23
24 baseCount := 10
25 baseDelta := 20 * Millisecond
26
27
28 if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" {
29
30
31
32
33
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
74
75 Sleep(Second / 2)
76 }
77 continue
78 }
79
80 Sleep(2 * delta)
81 select {
82 case <-ticker.C:
83 errs = append(errs, "Ticker did not shut down")
84 continue
85 default:
86
87 }
88
89
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
103 func TestTickerStopWithDirectInitialization(t *testing.T) {
104 c := make(chan Time)
105 tk := &Ticker{C: c}
106 tk.Stop()
107 }
108
109
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
125 func TestTick(t *testing.T) {
126
127 if got := Tick(-1); got != nil {
128 t.Errorf("Tick(-1) = %v; want nil", got)
129 }
130 }
131
132
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
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
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
179
180
181
182
183 done := make(chan bool)
184 AfterFunc(60*Second, func() { close(done) })
185
186
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
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
335
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
360
361
362 const (
363 sched = 10 * Millisecond
364 tries = 100
365 drainTries = 5
366 )
367
368
369
370
371
372 drain1 := func() {
373 for range drainTries {
374 select {
375 case <-C:
376 return
377 default:
378 }
379 Sleep(sched)
380 }
381 }
382
383
384
385
386 drainAsync := func() {
387 if synctimerchan {
388
389
390 return
391 }
392
393
394 drain1()
395 if isTicker {
396
397
398
399
400
401
402
403
404
405
406
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
457 tim.Stop()
458 noTick()
459
460
461 tim.Reset(10000 * Second)
462 noTick()
463
464 if synctimerchan {
465
466 tim.Reset(1)
467 Sleep(sched)
468 if l, c := len(C), cap(C); l != 0 || c != 0 {
469
470 }
471 assertTick()
472 } else {
473
474 tim.Reset(1)
475 assertTick()
476 Sleep(sched)
477 tim.Reset(10000 * Second)
478 drainAsync()
479 noTick()
480
481
482
483 tim.Reset(1)
484 assertLen()
485 assertTick()
486
487
488
489 tim.Stop()
490 drainAsync()
491 tim.Reset(1)
492 assertLen()
493 assertTick()
494 }
495
496
497
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
526 tim.Reset(10000 * Second)
527 drainAsync()
528
529
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
540 tim.Reset(20000 * Second)
541 Sleep(sched)
542 notDone(done)
543
544
545 tim.Reset(1)
546 waitDone(done)
547
548
549
550 if isTicker {
551 assertTick()
552 } else {
553 noTick()
554 }
555
556 tim.Stop()
557 drainAsync()
558 noTick()
559
560
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
594
595 select {
596 default:
597 case <-done:
598 }
599
600 select {
601 default:
602 case <-C:
603 }
604 }
605 notDone(done)
606
607
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
626
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
657
658
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
667
668
669
670
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
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