[client] Make tunnel notifier Close wait for queued events to drain

Close previously returned immediately after marking the notifier
closed, so deliverLoop could still be invoking listener/DNS callbacks
after Close returned. Close now blocks on a completion channel that
deliverLoop closes once it has drained the queue and exited.
This commit is contained in:
Zoltán Papp
2026-07-24 12:26:27 +02:00
parent 48a1782b8b
commit 1107c488a3
2 changed files with 7 additions and 1 deletions

View File

@@ -32,6 +32,7 @@ type Notifier struct {
cond *sync.Cond
queue *list.List
closed bool
done chan struct{}
listener listener.NetworkChangeListener
dnsManager dns.IosDnsManager
@@ -40,6 +41,7 @@ type Notifier struct {
func New(l listener.NetworkChangeListener, dm dns.IosDnsManager) *Notifier {
n := &Notifier{
queue: list.New(),
done: make(chan struct{}),
listener: l,
dnsManager: dm,
}
@@ -64,11 +66,14 @@ func (n *Notifier) ApplyDns(config string) {
n.enqueue(event{kind: eventDNS, payload: config})
}
// Close stops accepting new events and blocks until the delivery loop has
// drained all queued events and exited.
func (n *Notifier) Close() {
n.mu.Lock()
n.closed = true
n.cond.Signal()
n.mu.Unlock()
<-n.done
}
func (n *Notifier) enqueue(ev event) {
@@ -82,6 +87,7 @@ func (n *Notifier) enqueue(ev event) {
}
func (n *Notifier) deliverLoop() {
defer close(n.done)
for {
n.mu.Lock()
for n.queue.Len() == 0 && !n.closed {

View File

@@ -183,7 +183,7 @@ func TestCloseDrainsQueue(t *testing.T) {
}
n.Close()
require.Eventually(t, func() bool { return rec.count() == events }, 5*time.Second, time.Millisecond)
require.Equal(t, events, rec.count(), "Close must not return before all queued events are delivered")
n.OnNetworkChanged("after-close")
n.ApplyDns("after-close")