mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-10 15:39:07 +02:00
[client, android, ios] Coalesce peer list change notifications to the mobile listener (#7546)
* [client] Coalesce peer list change notifications to the mobile listener Every peer state change spawned a goroutine to call the platform listener. During a reconnect storm this pinned hundreds of OS threads in JNI and let the UI call back into the engine from each of them. Deliver peer list changes from a single goroutine per listener and collapse pending changes into the latest count. * [client] Cover a pending wake-up when the peer list deliverer is replaced The replacement test waited for the old callback to finish before swapping listeners, so it never exercised the stop check that runs after a wake-up. Block the old callback, queue a peer list change and swap while it is blocked, then assert the old listener never sees the new count. * [client] Signal peer list deliverer exit and wait for it in the test The replacement test sampled the old listener after a fixed sleep, so a late stale delivery could slip past it. Close a done channel when the deliverer goroutine returns and let the test wait on it instead. * [client] Drop the test-only peer list deliverer exit channel The done channel was only read by the replacement test. Production code cannot wait on it, since joining the deliverer would block on a mobile callback. The tests now drive the deliverer loop directly and check that setListener and removeListener close its stop channel.
This commit is contained in:
@@ -12,6 +12,8 @@ type notifier struct {
|
||||
serverStateLock sync.Mutex
|
||||
listenersLock sync.Mutex
|
||||
listener Listener
|
||||
peerListWake chan struct{}
|
||||
peerListStop chan struct{}
|
||||
currentClientState bool
|
||||
lastNotification ClientState
|
||||
lastNumberOfPeers int
|
||||
@@ -62,7 +64,6 @@ func (n *notifier) setNetworkAvailable(available bool) {
|
||||
func (n *notifier) setListener(listener Listener) {
|
||||
n.serverStateLock.Lock()
|
||||
lastNotification := n.effectiveState(n.lastNotification)
|
||||
numOfPeers := n.lastNumberOfPeers
|
||||
fqdnAddress := n.lastFqdnAddress
|
||||
address := n.lastIPAddress
|
||||
n.serverStateLock.Unlock()
|
||||
@@ -70,17 +71,19 @@ func (n *notifier) setListener(listener Listener) {
|
||||
n.listenersLock.Lock()
|
||||
defer n.listenersLock.Unlock()
|
||||
|
||||
n.stopPeerListDelivererLocked()
|
||||
n.listener = listener
|
||||
|
||||
listener.OnAddressChanged(fqdnAddress, address)
|
||||
notifyListener(listener, lastNotification)
|
||||
// run on go routine to avoid on Java layer to call go functions on same thread
|
||||
go listener.OnPeersListChanged(numOfPeers)
|
||||
n.startPeerListDelivererLocked(listener)
|
||||
n.wakePeerListDelivererLocked()
|
||||
}
|
||||
|
||||
func (n *notifier) removeListener() {
|
||||
n.listenersLock.Lock()
|
||||
defer n.listenersLock.Unlock()
|
||||
n.stopPeerListDelivererLocked()
|
||||
n.listener = nil
|
||||
}
|
||||
|
||||
@@ -178,15 +181,56 @@ func (n *notifier) peerListChanged(numOfPeers int) {
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.listenersLock.Lock()
|
||||
listener := n.listener
|
||||
n.listenersLock.Unlock()
|
||||
defer n.listenersLock.Unlock()
|
||||
n.wakePeerListDelivererLocked()
|
||||
}
|
||||
|
||||
if listener == nil {
|
||||
func (n *notifier) startPeerListDelivererLocked(listener Listener) {
|
||||
wake := make(chan struct{}, 1)
|
||||
stop := make(chan struct{})
|
||||
n.peerListWake = wake
|
||||
n.peerListStop = stop
|
||||
go n.deliverPeerListChanges(listener, wake, stop)
|
||||
}
|
||||
|
||||
func (n *notifier) stopPeerListDelivererLocked() {
|
||||
if n.peerListStop == nil {
|
||||
return
|
||||
}
|
||||
close(n.peerListStop)
|
||||
n.peerListStop = nil
|
||||
n.peerListWake = nil
|
||||
}
|
||||
|
||||
// run on go routine to avoid on Java layer to call go functions on same thread
|
||||
go listener.OnPeersListChanged(numOfPeers)
|
||||
func (n *notifier) wakePeerListDelivererLocked() {
|
||||
if n.peerListWake == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case n.peerListWake <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *notifier) deliverPeerListChanges(listener Listener, wake <-chan struct{}, stop <-chan struct{}) {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-wake:
|
||||
}
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
n.serverStateLock.Lock()
|
||||
numOfPeers := n.lastNumberOfPeers
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
listener.OnPeersListChanged(numOfPeers)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *notifier) localAddressChanged(fqdn, address string) {
|
||||
|
||||
Reference in New Issue
Block a user