Conversation
…droid drops dead links when Bluetooth goes off Found in the v0.28 device test: two Android phones a metre apart, both advertising the mesh service (a Mac scan saw both), never connected over Bluetooth for fifteen minutes. - Density counted every advert in range as mesh density. The estimate read 32 in a house (televisions, earbuds, watches), so the dense-mesh filters passed over 44% of peers, chosen by a hash of the address alone: each phone's address hashed under the line on the other (0.091 and 0.284), so each was passed over on every advert. Density now counts distinct mesh candidates (devices the discovery gate admits) in the last five seconds, and the pass-over is drawn per address per one-minute slot, mixed with the murmur3 finalizer so consecutive slots are independent draws. The unknown-device bootstrap keeps the all-advert count, which is what it is about. Same change on iOS, where the hash was Swift's per-process seed; both platforms compute the same bucket for the same id (pinned by tests on both sides). - Android: Bluetooth switched off under a running transport left every link in place, since the stack delivers no disconnect for most of them. The dead links counted against the connection cap, kept peers mapped to old addresses, and held GATT client registrations the stack had forgotten. The transport now reports each identified peer lost and clears the link state when it first sees the adapter off, mirroring iOS's dropLinksAfterRadioLoss, including the non-mesh cache that had marked a peer probed mid-recovery as not a mesh device for five minutes. BleDensityPolicy on both platforms (9 JVM tests, 8 Swift tests), a registry test, and the iOS restoration guard updated for the new call.
…uding a stack crash Found on the phones while validating the previous commit: the Infinix's Bluetooth stack crashed (SIGSEGV in libbluetooth_jni.so, connection_manager::on_connection_complete) as the app dialled the other phone. The stack restarted in under a second and took the app's GATT server, advertiser and pending connect with it. The transport polls the adapter once a minute from the scan watchdog, which read "enabled" throughout, so nothing was rebuilt: the pending connect pinned the peer's address as "connecting", and the other phone could not verify this one until the app restarted. Android reports a crash as an ordinary ON -> TURNING_OFF broadcast. The facade now registers for ACTION_STATE_CHANGED on the BLE handler once it runs, and unregisters behind the shutdown barrier: - TURNING_OFF / OFF: drop the dead links (once per outage), follow the dead scan locally, report BLE unavailable, arm recovery. - ON after an outage: run the recovery now (scan, GATT server, advertising) instead of waiting for the ladder's next rung (12 to 29 s measured). AdapterStateTransition maps the states (3 JVM tests); a Rust guard pins the registration after RUNNING, the unregistration after the barrier, and the radio-lost arm dropping links.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found in the v0.28 device test. Two Android phones a metre apart, both advertising the mesh service (a Mac scan saw both adverts), never connected over Bluetooth for fifteen minutes after Bluetooth was switched off and on. This PR fixes three causes (the third found while testing the first two). Both predate v0.28. Neither is about Wi-Fi Direct, but Bluetooth is the fallback carrier v0.28 leans on, so they belong in the release.
1. The dense-mesh filters counted every Bluetooth device in range (iOS and Android)
estimatedVisiblePeerCountcounts scan callbacks from every advertiser in range: televisions, earbuds, watches. Above 10, the RSSI filter, the rate limit andshouldProbabilisticallySkipengage. The skip passes over up to 80% of peers, chosen byhash(address)alone, so the same peers are passed over on every advert for as long as their address lasts. On iOS it isperipheral.hashValue, which is seeded per process.Measured on the phones: the estimate read 32, giving a skip share of 0.44. The Seeker's address hashes to 0.091 and the Infinix's to 0.284, both under 0.44. In the logs, each phone matches the other's service UUID every 30 s and then silently drops it before
Discovered device.Fix:
BleDensityPolicy, the same on both platforms.String.hashCodeput consecutive slots of one address in neighbouring buckets; the new test caught that. A skipped peer is reconsidered in the next minute.react_native_ios_restoration_commands_wait_for_powered_onnow pins the new call and explains the new reason.2. Android kept dead links after Bluetooth went off
When the adapter goes off, Android delivers no disconnect for most links. The facade reported BLE unavailable but kept every link, so:
BluetoothGattstill held a client registration the stack had forgotten (the Infinix showed five, andBtGatt.ContextMap: Context not found for ID 6repeated several times a second).Separately, a peer probed while its radio was still coming back serves no mesh service yet, so it landed in
verifiedNonMeshDevicesfor five minutes. On the phones, the Seeker probed the Infinix 17 s before the Infinix's recovery rebuilt its GATT service.Fix:
dropLinksAfterRadioLoss(), the Android equivalent of iOS's function of the same name. WhenstartScanningfirst sees the adapter off (once per outage, on the BLE thread), it:BluetoothGatt;3. Android never heard Bluetooth go off, come back, or crash (
7c4811f3)Found while validating 1 and 2. As the Infinix dialled the Seeker, Android's own Bluetooth stack crashed (SIGSEGV in
libbluetooth_jni.so,connection_manager::on_connection_complete, Infinix X670 on Android 13). The stack restarted in under a second and took the app's GATT server, advertiser and pending connect with it.The facade only learns about the adapter by polling
isEnabledfrom the 60 s scan watchdog. That reads "enabled" throughout a crash, so nothing was rebuilt:The same polling is why a plain toggle was noticed up to a minute late, and recovered 12 to 29 s after Bluetooth returned.
Fix: listen for
ACTION_STATE_CHANGEDon the BLE handler. Android reports a crash as the same ON → TURNING_OFF transition a toggle produces.AdapterStateTransitionmaps the states (3 JVM tests). A Rust guard pins the registration after RUNNING, the unregistration behind the shutdown barrier, and the radio-lost arm.On the phones (Infinix NOTE 12 / Android 13, Seeker / Android 15; build = #509's tree with this branch)
neighbor_lostexactly 1 per phone, headers 0 while off; relinked in 7, 21, 4 s; GATT client registrations stayed 1 to 4neighbor_lost; messages over Wi-Fi Direct p50 50 to 69 msbe01882a)neighbor_lost1 per phone, headers 0; Bluetooth back on relinked in 13 sKnown and not changed here (pre-existing; I'll file issues):
Related
#505 has the core half (
be01882a):ble_status_changed(false)now ends the core's Bluetooth peers. Before that, the stale entry suppressed a later Wi-Fi Directneighbor_lost. Each PR is correct without the other.Tests
BleDensityPolicyTest(JVM)BleDensityPolicyTests(Swift)StaleAddressRegistryTestdeviceIdstestswift testcargo test --workspace --libI could not run an iPhone. The iOS change is typechecked and unit-tested, and mirrors the Android logic line for line.