From 1107c488a3627b832e5fda555f95f79e19a2ab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Fri, 24 Jul 2026 12:26:27 +0200 Subject: [PATCH] [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. --- client/internal/tunnelnotifier/notifier.go | 6 ++++++ client/internal/tunnelnotifier/notifier_test.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/internal/tunnelnotifier/notifier.go b/client/internal/tunnelnotifier/notifier.go index 3798c4d64..b62923a6e 100644 --- a/client/internal/tunnelnotifier/notifier.go +++ b/client/internal/tunnelnotifier/notifier.go @@ -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 { diff --git a/client/internal/tunnelnotifier/notifier_test.go b/client/internal/tunnelnotifier/notifier_test.go index 02a68abbb..ffbcdc15c 100644 --- a/client/internal/tunnelnotifier/notifier_test.go +++ b/client/internal/tunnelnotifier/notifier_test.go @@ -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")