Skip to content

Commit 3f9f183

Browse files
committed
fix: Close notifier Poll goroutine on stop
Fix towards #3221.
1 parent d27076c commit 3f9f183

File tree

1 file changed

+29
-4
lines changed

1 file changed

+29
-4
lines changed

coderd/autobuild/notify/notifier.go

+29-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,26 @@ 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 := <-ticker:
81+
n.pollOnce(t)
82+
}
6483
}
6584
}
6685

86+
func (n *Notifier) Close() error {
87+
n.cancel()
88+
<-n.pollDone
89+
return nil
90+
}
91+
6792
func (n *Notifier) pollOnce(tick time.Time) {
6893
n.lock.Lock()
6994
defer n.lock.Unlock()

0 commit comments

Comments
 (0)