Skip to content

Commit c736874

Browse files
committed
1 parent 302b426 commit c736874

File tree

2 files changed

+159
-18
lines changed

2 files changed

+159
-18
lines changed

go/time/timer.go go/time/sleep.go

+36-18
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,58 @@ package time
22

33
import (
44
"time"
5-
6-
"github.com/searKing/golang/go/sync/atomic"
75
)
86

97
// https://github.com/golang/go/issues/27169
108
// Timer to fix time: Timer.Stop documentation example easily leads to deadlocks
119
type Timer struct {
1210
*time.Timer
13-
chanConsumed atomic.Bool
1411
}
1512

16-
// saw channel read, must be called after receiving value from timer chan
17-
// an example case is AfterFunc bellow.
18-
func (t *Timer) ChanConsumed() {
19-
t.chanConsumed.Store(true)
13+
// Stop prevents the Timer from firing, with the channel drained.
14+
// Stop ensures the channel is empty after a call to Stop.
15+
// Stop == std [Stop + drain]
16+
// It returns true if the call stops the timer, false if the timer has already
17+
// expired or been stopped.
18+
// Stop does not close the channel, to prevent a read from the channel succeeding
19+
// incorrectly.
20+
func (t *Timer) Stop() bool {
21+
if t.Timer == nil {
22+
panic("time: Stop called on uninitialized Timer")
23+
}
24+
25+
active := t.Timer.Stop()
26+
if !active {
27+
// drain the channel, prevents the Timer from blocking on Send to t.C by sendTime, t.C is reused.
28+
// The underlying Timer is not recovered by the garbage collector until the timer fires.
29+
// consume the channel only once for the channel can be triggered only one time at most before Stop is called.
30+
L:
31+
for {
32+
select {
33+
case _, ok := <-t.Timer.C:
34+
if !ok {
35+
break L
36+
}
37+
default:
38+
break L
39+
}
40+
}
41+
}
42+
return active
2043
}
2144

2245
// Reset changes the timer to expire after duration d.
46+
// Reset can be invoked anytime, which enhances std time.Reset
47+
// Reset == std [Stop + drain + Reset]
2348
// It returns true if the timer had been active, false if the timer had
2449
// expired or been stopped.
25-
// Reset can be invoked anytime, which enhances std time.Reset
26-
// that should be invoked only on stopped or expired timers with drained channels,
2750
func (t *Timer) Reset(d time.Duration) bool {
28-
ret := t.Stop()
29-
if !ret && !t.chanConsumed.Load() {
30-
// drain the channel, prevents the Timer from blocking on Send to t.C by sendTime, t.C is reused.
31-
// The underlying Timer is not recovered by the garbage collector until the timer fires.
32-
// consume the channel only once for the channel can be triggered only one time at most before Stop is called.
33-
<-t.C
51+
if t.Timer == nil {
52+
panic("time: Reset called on uninitialized Timer")
3453
}
54+
active := t.Stop()
3555
t.Timer.Reset(d)
36-
t.chanConsumed.Store(false)
37-
return ret
56+
return active
3857
}
3958

4059
func NewTimer(d time.Duration) *Timer {
@@ -56,7 +75,6 @@ func After(d time.Duration) <-chan time.Time {
5675
func AfterFunc(d time.Duration, f func()) *Timer {
5776
t := &Timer{}
5877
t.Timer = time.AfterFunc(d, func() {
59-
t.ChanConsumed()
6078
f()
6179
})
6280
return t

go/time/sleep_test.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package time_test
2+
3+
import (
4+
"errors"
5+
"runtime"
6+
"strings"
7+
"sync"
8+
"testing"
9+
"time"
10+
11+
time_ "github.com/searKing/golang/go/time"
12+
)
13+
14+
func benchmark(b *testing.B, bench func(n int)) {
15+
// Create equal number of garbage timers on each P before starting
16+
// the benchmark.
17+
var wg sync.WaitGroup
18+
garbageAll := make([][]*time_.Timer, runtime.GOMAXPROCS(0))
19+
for i := range garbageAll {
20+
wg.Add(1)
21+
go func(i int) {
22+
defer wg.Done()
23+
garbage := make([]*time_.Timer, 1<<15)
24+
for j := range garbage {
25+
garbage[j] = time_.AfterFunc(time.Hour, nil)
26+
}
27+
garbageAll[i] = garbage
28+
}(i)
29+
}
30+
wg.Wait()
31+
32+
b.ResetTimer()
33+
b.RunParallel(func(pb *testing.PB) {
34+
for pb.Next() {
35+
bench(1000)
36+
}
37+
})
38+
b.StopTimer()
39+
40+
for _, garbage := range garbageAll {
41+
for _, t := range garbage {
42+
t.Stop()
43+
}
44+
}
45+
}
46+
func BenchmarkReset(b *testing.B) {
47+
benchmark(b, func(n int) {
48+
t := time_.NewTimer(time.Hour)
49+
for i := 0; i < n; i++ {
50+
t.Reset(time.Hour)
51+
}
52+
t.Stop()
53+
})
54+
}
55+
56+
func testReset(d time.Duration) error {
57+
t0 := time.NewTimer(2 * d)
58+
time.Sleep(d)
59+
if !t0.Reset(3 * d) {
60+
return errors.New("resetting unfired timer returned false")
61+
}
62+
time.Sleep(2 * d)
63+
select {
64+
case <-t0.C:
65+
return errors.New("timer fired early")
66+
default:
67+
}
68+
time.Sleep(2 * d)
69+
select {
70+
case <-t0.C:
71+
default:
72+
return errors.New("reset timer did not fire")
73+
}
74+
75+
if t0.Reset(50 * time.Millisecond) {
76+
return errors.New("resetting expired timer returned true")
77+
}
78+
return nil
79+
}
80+
81+
func TestReset(t *testing.T) {
82+
// We try to run this test with increasingly larger multiples
83+
// until one works so slow, loaded hardware isn't as flaky,
84+
// but without slowing down fast machines unnecessarily.
85+
const unit = 25 * time.Millisecond
86+
tries := []time.Duration{
87+
1 * unit,
88+
3 * unit,
89+
7 * unit,
90+
15 * unit,
91+
}
92+
var err error
93+
for _, d := range tries {
94+
err = testReset(d)
95+
if err == nil {
96+
t.Logf("passed using duration %v", d)
97+
return
98+
}
99+
}
100+
t.Error(err)
101+
}
102+
103+
func checkZeroPanicString(t *testing.T) {
104+
e := recover()
105+
s, _ := e.(string)
106+
if want := "called on uninitialized Timer"; !strings.Contains(s, want) {
107+
t.Errorf("panic = %v; want substring %q", e, want)
108+
}
109+
}
110+
111+
func TestZeroTimerResetPanics(t *testing.T) {
112+
defer checkZeroPanicString(t)
113+
var tr time_.Timer
114+
tr.Reset(1)
115+
}
116+
117+
func TestTimer_Reset(t *testing.T) {
118+
tr := time_.NewTimer(25 * time.Millisecond)
119+
defer func() {
120+
tr.Stop()
121+
}()
122+
<-tr.C
123+
}

0 commit comments

Comments
 (0)