Skip to content

Commit a0320f4

Browse files
authored
fix: Close notifier Poll goroutine on stop (#3252)
Fix towards #3221.
1 parent 6377f17 commit a0320f4

File tree

2 files changed

+34
-5
lines changed

2 files changed

+34
-5
lines changed

coderd/autobuild/notify/notifier.go

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package notify
22

33
import (
4+
"context"
45
"sort"
56
"sync"
67
"time"
78
)
89

910
// Notifier calls a Condition at most once for each count in countdown.
1011
type Notifier struct {
12+
ctx context.Context
13+
cancel context.CancelFunc
14+
pollDone chan struct{}
15+
1116
lock sync.Mutex
1217
condition Condition
1318
notifiedAt map[time.Duration]bool
@@ -28,11 +33,14 @@ type Condition func(now time.Time) (deadline time.Time, callback func())
2833
// Notify is a convenience function that initializes a new Notifier
2934
// with the given condition, interval, and countdown.
3035
// It is the responsibility of the caller to call close to stop polling.
31-
func Notify(cond Condition, interval time.Duration, countdown ...time.Duration) (close func()) {
36+
func Notify(cond Condition, interval time.Duration, countdown ...time.Duration) (closeFunc func()) {
3237
notifier := New(cond, countdown...)
3338
ticker := time.NewTicker(interval)
3439
go notifier.Poll(ticker.C)
35-
return ticker.Stop
40+
return func() {
41+
ticker.Stop()
42+
_ = notifier.Close()
43+
}
3644
}
3745

3846
// New returns a Notifier that calls cond once every time it polls.
@@ -45,7 +53,11 @@ func New(cond Condition, countdown ...time.Duration) *Notifier {
4553
return ct[i] < ct[j]
4654
})
4755

56+
ctx, cancel := context.WithCancel(context.Background())
4857
n := &Notifier{
58+
ctx: ctx,
59+
cancel: cancel,
60+
pollDone: make(chan struct{}),
4961
countdown: ct,
5062
condition: cond,
5163
notifiedAt: make(map[time.Duration]bool),
@@ -57,13 +69,29 @@ func New(cond Condition, countdown ...time.Duration) *Notifier {
5769
// Poll polls once immediately, and then once for every value from ticker.
5870
// Poll exits when ticker is closed.
5971
func (n *Notifier) Poll(ticker <-chan time.Time) {
72+
defer close(n.pollDone)
73+
6074
// poll once immediately
6175
n.pollOnce(time.Now())
62-
for t := range ticker {
63-
n.pollOnce(t)
76+
for {
77+
select {
78+
case <-n.ctx.Done():
79+
return
80+
case t, ok := <-ticker:
81+
if !ok {
82+
return
83+
}
84+
n.pollOnce(t)
85+
}
6486
}
6587
}
6688

89+
func (n *Notifier) Close() error {
90+
n.cancel()
91+
<-n.pollDone
92+
return nil
93+
}
94+
6795
func (n *Notifier) pollOnce(tick time.Time) {
6896
n.lock.Lock()
6997
defer n.lock.Unlock()

coderd/autobuild/notify/notifier_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ func TestNotifier(t *testing.T) {
9090
}
9191
var wg sync.WaitGroup
9292
go func() {
93+
defer wg.Done()
9394
n := notify.New(cond, testCase.Countdown...)
95+
defer n.Close()
9496
n.Poll(ch)
95-
wg.Done()
9697
}()
9798
wg.Add(1)
9899
for _, tick := range testCase.Ticks {

0 commit comments

Comments
 (0)