network(networkmanager): rebuild the Wi-Fi list once per scan (#3818)

This commit is contained in:
Evgeny Zemtsov
2026-10-19 15:39:56 -04:00
committed by GitHub
parent d04525b2bd
commit bb3e3adaab
3 changed files with 80 additions and 2 deletions
@@ -4,6 +4,7 @@ import (
"fmt"
"maps"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/Wifx/gonetworkmanager/v2"
@@ -80,6 +81,10 @@ type NetworkManagerBackend struct {
sigWG sync.WaitGroup
stopChan chan struct{}
wifiRefreshTimer *time.Timer
wifiRefreshMu sync.Mutex
wifiRefreshWG sync.WaitGroup
secretAgent *SecretAgent
promptBroker PromptBroker
@@ -1,6 +1,8 @@
package network
import (
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/Wifx/gonetworkmanager/v2"
"github.com/godbus/dbus/v5"
@@ -10,6 +12,8 @@ const (
dbusNMSettingsPath = "/org/freedesktop/NetworkManager/Settings"
dbusNMSettingsInterface = "org.freedesktop.NetworkManager.Settings"
dbusNMSettingsConnectionInterface = "org.freedesktop.NetworkManager.Settings.Connection"
wifiNetworksUpdateDelay = 500 * time.Millisecond
)
func (b *NetworkManagerBackend) startSignalPump() error {
@@ -287,7 +291,8 @@ func (b *NetworkManagerBackend) stopSignalPump() {
}
b.sigWG.Wait()
b.cancelWiFiNetworksUpdate()
b.wifiRefreshWG.Wait()
}
func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) {
@@ -537,7 +542,7 @@ func (b *NetworkManagerBackend) handleWiFiChange(changes map[string]dbus.Variant
needsStateUpdate = true
needsNetworkUpdate = true
case "AccessPoints":
needsNetworkUpdate = true
b.scheduleWiFiNetworksUpdate()
default:
continue
}
@@ -547,6 +552,7 @@ func (b *NetworkManagerBackend) handleWiFiChange(changes map[string]dbus.Variant
b.updateWiFiState()
}
if needsNetworkUpdate {
b.cancelWiFiNetworksUpdate()
b.updateWiFiNetworks()
}
if needsStateUpdate || needsNetworkUpdate {
@@ -556,6 +562,41 @@ func (b *NetworkManagerBackend) handleWiFiChange(changes map[string]dbus.Variant
}
}
// A scan announces each new access point on its own, so rebuild the list
// once per burst instead of once per access point.
func (b *NetworkManagerBackend) scheduleWiFiNetworksUpdate() {
b.wifiRefreshMu.Lock()
defer b.wifiRefreshMu.Unlock()
if b.wifiRefreshTimer != nil {
return
}
b.wifiRefreshWG.Add(1)
b.wifiRefreshTimer = time.AfterFunc(wifiNetworksUpdateDelay, func() {
defer b.wifiRefreshWG.Done()
b.wifiRefreshMu.Lock()
b.wifiRefreshTimer = nil
b.wifiRefreshMu.Unlock()
b.updateWiFiNetworks()
if b.onStateChange != nil {
b.onStateChange()
}
})
}
func (b *NetworkManagerBackend) cancelWiFiNetworksUpdate() {
b.wifiRefreshMu.Lock()
defer b.wifiRefreshMu.Unlock()
if b.wifiRefreshTimer == nil || !b.wifiRefreshTimer.Stop() {
return
}
b.wifiRefreshTimer = nil
b.wifiRefreshWG.Done()
}
func (b *NetworkManagerBackend) handleAccessPointChange(changes map[string]dbus.Variant) {
_, hasStrength := changes["Strength"]
if !hasStrength {
@@ -1,7 +1,9 @@
package network
import (
"sync/atomic"
"testing"
"time"
mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
"github.com/Wifx/gonetworkmanager/v2"
@@ -98,3 +100,33 @@ func TestNetworkManagerBackend_HandleDeviceChange_UnmanagedRefreshesHotspotState
defer backend.stateMutex.RUnlock()
assert.False(t, backend.state.HotspotAvailable, "availability must be recomputed when the only AP-capable radio becomes unmanaged")
}
func TestNetworkManagerBackend_HandleWiFiChange_CoalescesAccessPointBurst(t *testing.T) {
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
mockWiFi := mock_gonetworkmanager.NewMockDeviceWireless(t)
backend, err := NewNetworkManagerBackend(mockNM)
assert.NoError(t, err)
backend.settings = emptySettingsMock(t)
backend.wifiDevice = mockWiFi
backend.wifiDev = mockWiFi
var stateChangeCalls atomic.Int32
backend.onStateChange = func() {
stateChangeCalls.Add(1)
}
mockWiFi.EXPECT().GetAccessPoints().Return([]gonetworkmanager.AccessPoint{}, nil).Once()
changes := map[string]dbus.Variant{
"AccessPoints": dbus.MakeVariant([]dbus.ObjectPath{}),
}
for range 5 {
backend.handleWiFiChange(changes)
}
assert.Zero(t, stateChangeCalls.Load(), "the list must not be rebuilt per access point")
assert.Eventually(t, func() bool {
return stateChangeCalls.Load() == 1
}, 4*wifiNetworksUpdateDelay, 10*time.Millisecond)
}