Merge pull request #827 from a1denvalu3/fix/stale-peer-cleanup

Fix stale peer lifecycle cleanup
This commit is contained in:
callebtc 2026-07-30 02:41:13 +02:00 committed by GitHub
commit 33538fa9e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 5 deletions

View File

@ -335,7 +335,7 @@ class PeerManager {
// Remove stale peer IDs
stalePeerIDs.forEach { stalePeerID ->
removePeer(stalePeerID, notifyDelegate = false)
removePeer(stalePeerID, notifyPeerList = false)
}
// Check if this is a new peer announcement
@ -371,7 +371,7 @@ class PeerManager {
/**
* Remove peer
*/
fun removePeer(peerID: String, notifyDelegate: Boolean = true) {
fun removePeer(peerID: String, notifyPeerList: Boolean = true) {
val removed = peers.remove(peerID)
peerRSSI.remove(peerID)
announcedPeers.remove(peerID)
@ -380,10 +380,13 @@ class PeerManager {
// Also remove fingerprint mappings
fingerprintManager.removePeer(peerID)
if (notifyDelegate && removed != null) {
// Notify specific removal event then list update
if (removed != null) {
// Lifecycle cleanup must always run. Callers may suppress only the
// intermediate peer-list update while atomically replacing a peer.
try { delegate?.onPeerRemoved(peerID) } catch (_: Exception) {}
notifyPeerListUpdate()
if (notifyPeerList) {
notifyPeerListUpdate()
}
}
}

View File

@ -67,6 +67,11 @@ class MeshGraphService private constructor() {
nicknames.remove(peerID)
announcements.remove(peerID)
lastUpdate.remove(peerID)
announcements.keys.toList().forEach { originPeerID ->
announcements.computeIfPresent(originPeerID) { _, neighbors ->
neighbors - peerID
}
}
publishSnapshot()
}
}

View File

@ -1,6 +1,7 @@
package com.bitchat
import com.bitchat.android.mesh.PeerManager
import com.bitchat.android.mesh.PeerManagerDelegate
import com.bitchat.android.model.PeerCapabilities
import junit.framework.TestCase.assertEquals
import org.junit.Test
@ -167,6 +168,28 @@ class PeerManagerTest {
assertEquals(testUsers.size - 3, numberOfAllPeers)
}
@Test
fun suppressing_peer_list_update_still_notifies_peer_removal() {
val removedPeers = mutableListOf<String>()
var peerListUpdates = 0
peerManager.delegate = object : PeerManagerDelegate {
override fun onPeerListUpdated(peerIDs: List<String>) {
peerListUpdates++
}
override fun onPeerRemoved(peerID: String) {
removedPeers.add(peerID)
}
}
peerManager.addOrUpdatePeer("peer-stale", "alice")
peerListUpdates = 0
peerManager.removePeer("peer-stale", notifyPeerList = false)
assertEquals(listOf("peer-stale"), removedPeers)
assertEquals(0, peerListUpdates)
}
@Test
fun last_seen_updated_correctly() {
testUsers.forEach { peerID, _ ->

View File

@ -123,4 +123,19 @@ class MeshGraphServiceTest {
assertFalse(snapshot.edges.isEmpty())
assertNotNull(snapshot.edges.find { (it.a == "PeerA" && it.b == "PeerB") || (it.a == "PeerB" && it.b == "PeerA") })
}
@Test
fun removePeer_RemovesInboundAndOutboundGraphReferences() {
service.updateFromAnnouncement("PeerA", "Alice", listOf("PeerB"), 100UL)
service.updateFromAnnouncement("PeerB", "Bob", listOf("PeerA", "PeerC"), 100UL)
service.updateFromAnnouncement("PeerC", "Carol", listOf("PeerB"), 100UL)
service.removePeer("PeerB")
val snapshot = service.graphState.value
assertFalse(snapshot.nodes.any { it.peerID == "PeerB" })
assertFalse(snapshot.edges.any { it.a == "PeerB" || it.b == "PeerB" })
assertTrue(snapshot.nodes.any { it.peerID == "PeerA" })
assertTrue(snapshot.nodes.any { it.peerID == "PeerC" })
}
}