Skip to content

fix(ble): ensure GATT cleanup runs under NonCancellable on cancellation#5207

Open
jdogg172 wants to merge 3 commits intomeshtastic:mainfrom
jdogg172:fix/ble-reconnect-crash
Open

fix(ble): ensure GATT cleanup runs under NonCancellable on cancellation#5207
jdogg172 wants to merge 3 commits intomeshtastic:mainfrom
jdogg172:fix/ble-reconnect-crash

Conversation

@jdogg172
Copy link
Copy Markdown

Summary

Fixes 2 crash-level bugs in the Android BLE reconnection stack.

Changes

BleRadioTransport.kt

  • CancellationException / GATT 133 fix: discoverServicesAndSetupCharacteristics() had a bare catch (e: Exception) that swallowed CancellationException, causing disconnect() to be skipped when the scope was cancelled. Skipping disconnect() leaks the BluetoothGatt handle and reliably triggers GATT status 133 on the next reconnect. Now re-throws CancellationException after running disconnect() under NonCancellable. Error path disconnect also wrapped in NonCancellable.

KableBleConnection.kt

  • Thread visibility: Added @Volatile to peripheral, stateJob, and connectionScope fields to ensure cross-thread visibility between connect() and disconnect() callers.

Tests

Added BleRadioTransportReconnectCrashTest with 4 new tests covering GATT handle cleanup, CancellationException path, failure disconnect, and transient-only disconnect signalling. All 108 tests pass (BUILD SUCCESSFUL on JVM).

…on discoverServicesAndSetupCharacteristics() caught bare Exception which swallowed CancellationException, causing disconnect() to be skipped when the scope was cancelled. Skipping disconnect() leaks the BluetoothGatt handle and reliably triggers GATT status 133 on the next reconnect attempt. Now re-throws CancellationException after running disconnect() under NonCancellable, and also wraps the error-path disconnect() in NonCancellable to guard against late cancellation during cleanup. Also mark KableBleConnection.peripheral/stateJob/connectionScope as @volatile to ensure cross-thread visibility between connect() and disconnect() callers.
…fix Tests verify: - close() calls disconnect() to release GATT handle (prevents status 133 on reconnect) - disconnect() is called when profile setup throws CancellationException - disconnect() is called on connection failure - only transient (non-permanent) onDisconnect is signalled after failure threshold All 4 new tests pass alongside 104 existing tests (BUILD SUCCESSFUL).
Copilot AI review requested due to automatic review settings April 21, 2026 21:07
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 21, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the BLE reconnection path by ensuring GATT cleanup (disconnect) still runs when coroutines are cancelled, preventing leaked BluetoothGatt handles that can lead to GATT status 133 on subsequent reconnects. It also adds tests targeting the reconnect crash scenarios and improves cross-thread visibility in the Kable-based BLE connection implementation.

Changes:

  • Ensure BleRadioTransport.discoverServicesAndSetupCharacteristics() disconnects under NonCancellable and rethrows CancellationException.
  • Add @Volatile to key fields in KableBleConnection to improve cross-thread visibility between connect/disconnect callers.
  • Add BleRadioTransportReconnectCrashTest with coverage for cleanup and reconnect signaling behaviors.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioTransport.kt Ensures disconnect cleanup runs under NonCancellable, including the cancellation path.
core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleConnection.kt Adds @Volatile to shared mutable fields used across connect/disconnect.
core/network/src/commonTest/kotlin/org/meshtastic/core/network/radio/BleRadioTransportReconnectCrashTest.kt Adds regression tests for cancellation + failure cleanup and transient disconnect behavior.

Comment on lines +94 to +98
@Volatile private var peripheral: Peripheral? = null

@Volatile private var stateJob: Job? = null

@Volatile private var connectionScope: CoroutineScope? = null
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: added import kotlin.concurrent.Volatile at the top of KableBleConnection.kt so the annotation resolves correctly in commonMain.

Comment on lines +64 to +65
private val testScope = TestScope()
private val scanner = FakeBleScanner()
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: removed the unused testScope = TestScope() field. Also restored the SharedFlow, asSharedFlow, and advanceTimeBy imports that the CancellingProfileBleConnection inner class and virtual-time tests depend on.

- KableBleConnection: add missing kotlin.concurrent.Volatile import (was causing compile error in commonMain)
- BleRadioTransportReconnectCrashTest: remove unused testScope field; restore SharedFlow/asSharedFlow/advanceTimeBy imports that were needed by CancellingProfileBleConnection inner class
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants