Skip to content

Commit 5b8ecf7

Browse files
committed
Add AfterFunc to Deadline
1 parent 7c9759e commit 5b8ecf7

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

deadline/deadline.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ type Deadline struct {
3030
deadline time.Time
3131
state deadlineState
3232
pending uint8
33+
cbs map[int]func()
34+
nextCbID int
3335
}
3436

3537
// New creates new deadline timer.
3638
func New() *Deadline {
3739
return &Deadline{
3840
done: make(chan struct{}),
41+
cbs: map[int]func(){},
3942
}
4043
}
4144

@@ -49,11 +52,40 @@ func (d *Deadline) timeout() {
4952

5053
d.state = deadlineExceeded
5154
done := d.done
55+
for _, cb := range d.cbs {
56+
go cb()
57+
}
58+
clear(d.cbs)
5259
d.mu.Unlock()
5360

5461
close(done)
5562
}
5663

64+
// AfterFunc attaches a function to the deadline.
65+
// The functions will be triggered on deadline exceeded.
66+
// If the deadline is reset, the functions are skipped.
67+
// Attached functions are only triggered once.
68+
func (d *Deadline) AfterFunc(cb func()) func() bool {
69+
d.mu.Lock()
70+
defer d.mu.Unlock()
71+
d.cbs[d.nextCbID] = cb
72+
usedID := d.nextCbID
73+
cancel := func() bool {
74+
d.mu.Lock()
75+
defer d.mu.Unlock()
76+
if _, has := d.cbs[usedID]; has {
77+
delete(d.cbs, usedID)
78+
79+
return true
80+
}
81+
82+
return false
83+
}
84+
d.nextCbID++
85+
86+
return cancel
87+
}
88+
5789
// Set new deadline. Zero value means no deadline.
5890
func (d *Deadline) Set(setTo time.Time) {
5991
d.mu.Lock()
@@ -73,6 +105,7 @@ func (d *Deadline) Set(setTo time.Time) {
73105
if setTo.IsZero() {
74106
d.pending--
75107
d.state = deadlineStopped
108+
clear(d.cbs)
76109

77110
return
78111
}
@@ -91,6 +124,10 @@ func (d *Deadline) Set(setTo time.Time) {
91124
d.pending--
92125
d.state = deadlineExceeded
93126
close(d.done)
127+
for _, cb := range d.cbs {
128+
go cb()
129+
}
130+
clear(d.cbs)
94131
}
95132

96133
// Done receives deadline signal.

deadline/deadline_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package deadline
55

66
import (
77
"context"
8+
"sync/atomic"
89
"testing"
910
"time"
1011

@@ -93,6 +94,68 @@ func TestDeadline(t *testing.T) {
9394
expectedCalls := []byte{0}
9495
assert.Equal(t, expectedCalls, calls, "Wrong order of deadline signal")
9596
})
97+
98+
t.Run("DeadlineAfterFuncExceedFuture", func(t *testing.T) {
99+
now := time.Now()
100+
101+
d := New()
102+
var trigger atomic.Int32
103+
d.AfterFunc(func() {
104+
trigger.Add(1)
105+
})
106+
d.Set(now.Add(10 * time.Millisecond))
107+
<-time.After(20 * time.Millisecond)
108+
d.Set(now.Add(10 * time.Millisecond))
109+
<-time.After(20 * time.Millisecond)
110+
111+
assert.Equal(t, int32(1), trigger.Load(), "Function triggered wrong number of time")
112+
})
113+
114+
t.Run("DeadlineAfterFuncExceedPast", func(t *testing.T) {
115+
now := time.Now()
116+
117+
d := New()
118+
var trigger atomic.Int32
119+
d.AfterFunc(func() {
120+
trigger.Add(1)
121+
})
122+
d.Set(now.Add(10 * time.Millisecond))
123+
d.Set(now.Add(-10 * time.Millisecond))
124+
<-time.After(20 * time.Millisecond)
125+
126+
assert.Equal(t, int32(1), trigger.Load(), "Function triggered wrong number of time")
127+
})
128+
129+
t.Run("DeadlineAfterFuncCancel", func(t *testing.T) {
130+
now := time.Now()
131+
132+
d := New()
133+
var trigger atomic.Int32
134+
d.AfterFunc(func() {
135+
trigger.Add(1)
136+
})
137+
138+
d.Set(now.Add(10 * time.Millisecond))
139+
d.Set(time.Time{})
140+
<-time.After(20 * time.Millisecond)
141+
142+
assert.Equal(t, int32(0), trigger.Load(), "Function triggered wrong number of time")
143+
})
144+
145+
t.Run("DeadlineAfterFuncDetach", func(t *testing.T) {
146+
now := time.Now()
147+
148+
d := New()
149+
var trigger atomic.Int32
150+
detach := d.AfterFunc(func() {
151+
trigger.Add(1)
152+
})
153+
detach()
154+
d.Set(now.Add(10 * time.Millisecond))
155+
<-time.After(20 * time.Millisecond)
156+
157+
assert.Equal(t, int32(0), trigger.Load(), "Function triggered wrong number of time")
158+
})
96159
}
97160

98161
func sendOnDone(ctx context.Context, done <-chan struct{}, dest chan byte, val byte) {

0 commit comments

Comments
 (0)