[client] Disconnect daemon on GUI quit via async Down

The tray Quit menu now disconnects the daemon before exiting instead of
only tearing down the GUI. A new DownAsync RPC lets the daemon start the
teardown and return immediately: beginDown cancels the connection under
the mutex (so it cannot reconnect), then finishDown (the retry-goroutine
wait and status reset) runs on a background goroutine. handleQuit aborts
any in-flight profile switch first (so a queued Up cannot reconnect during
teardown) and calls DownAsync so quitting never blocks on the engine
shutdown.
This commit is contained in:
Zoltán Papp
2026-07-16 11:10:40 +02:00
parent 8f901f8899
commit 1fcfc446a9
7 changed files with 358 additions and 118 deletions
+11
View File
@@ -177,6 +177,17 @@ func (s *Connection) Down(ctx context.Context) error {
return nil
}
func (s *Connection) DownAsync(ctx context.Context) error {
cli, err := s.conn.Client()
if err != nil {
return err
}
if _, err = cli.DownAsync(ctx, &proto.DownAsyncRequest{}); err != nil {
return s.classifyDaemonError(err)
}
return nil
}
// OpenURL opens url in an external browser; the embedded webview blocks
// window.open, so the SSO verification page can't pop inline. Honors $BROWSER
// before the platform default.
+20 -1
View File
@@ -30,6 +30,8 @@ const (
statusError = "Error"
quitDownTimeout = 5 * time.Second
urlGitHubRepo = "https://github.com/netbirdio/netbird"
urlGitHubReleases = "https://github.com/netbirdio/netbird/releases/latest"
urlDocs = "https://docs.netbird.io"
@@ -446,11 +448,28 @@ func (t *Tray) buildMenu() *application.Menu {
menu.AddSeparator()
menu.Add(t.loc.T("tray.menu.quit")).
SetAccelerator("CmdOrCtrl+Q").
OnClick(func(*application.Context) { t.app.Quit() })
OnClick(func(*application.Context) { t.handleQuit() })
return menu
}
func (t *Tray) handleQuit() {
t.profileMu.Lock()
if t.switchCancel != nil {
t.switchCancel()
t.switchCancel = nil
}
t.profileMu.Unlock()
t.svc.DaemonFeed.CancelProfileSwitch()
ctx, cancel := context.WithTimeout(context.Background(), quitDownTimeout)
defer cancel()
if err := t.svc.Connection.DownAsync(ctx); err != nil {
log.Errorf("disconnect on quit: %v", err)
}
t.app.Quit()
}
// handleConnect receives the clicked item from the buildMenu closure —
// t.upItem is menuMu-guarded and must not be read here.
func (t *Tray) handleConnect(upItem *application.MenuItem) {