Skip to content

fix(ble): harden reconnect sessions and Android scan lifecycle#6384

Merged
jamesarich merged 3 commits into
meshtastic:mainfrom
jeremiah-k:bugfix/ble-reconnect-lifecycle
Jul 23, 2026
Merged

fix(ble): harden reconnect sessions and Android scan lifecycle#6384
jamesarich merged 3 commits into
meshtastic:mainfrom
jeremiah-k:bugfix/ble-reconnect-lifecycle

Conversation

@jeremiah-k

@jeremiah-k jeremiah-k commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Overview

This hardens BLE reconnect behavior across transport-session replacement and Android scan throttling.

A write captured from an old BLE session could continue retrying after that session had already been disconnected or replaced. Bonded reconnect also performed two sequential scans before falling back to Android's bonded device handle, increasing scan-start pressure during rapid device switching. Those reconnect scans share Android's process-wide scan quota with user-initiated discovery, but the callers were not coordinated.

This is a focused follow-up to #5795 and #5960. It also extends the Android scan-start failure handling from #6019 to cover the process-wide SCAN_FAILED_SCANNING_TOO_FREQUENTLY limit.

The change makes write retry authority session-aware, reduces bonded reconnect discovery to one bounded scan, and coordinates Android scan starts through one shared rolling quota.

Review Guide

The three commits are intended to be reviewed in order:

  1. Retired-session writes — stops BLE write retries when the captured profile or transport session is no longer active.
  2. Bonded reconnect scans — replaces the targeted-plus-escalated scan sequence with one bounded address-filtered scan and preserves bonded-handle fallback.
  3. Android scan quota — coordinates scan starts across reconnect and UI discovery, surfaces a localized rounded-up cooldown, and isolates the process-wide limiter from unrelated scanner unit tests.

Key Changes

Session-aware write retries

  • Bound write retries to the captured Meshtastic profile and transport session.
  • Re-check retry authority before each retry and after retry backoff.
  • Stop retrying when a remote disconnect or replacement connection retires the captured session.
  • Preserve the existing retry behavior while the session remains active.
  • Distinguish stale-session failures from active-session write failures in diagnostics.

Bonded reconnect discovery

  • Replace the targeted-scan-then-escalated-scan sequence with one bounded, address-filtered scan.
  • Prefer a fresh scanned advertisement when one is available.
  • Fall back to the bonded device handle when the bounded scan misses.
  • Preserve the existing bounded autoConnect connection attempt.
  • Leave non-bonded discovery behavior unchanged.

Android scan-start coordination

  • Add a process-wide rolling limiter shared by reconnect and UI discovery.
  • Enforce Android's five scan starts per 30-second application quota before entering the platform scanner.
  • Serialize concurrent reservations so simultaneous callers cannot exceed the quota.
  • Reserve a platform scan start exactly once, within the requested timeout and before advertisement collection begins.
  • Surface a typed scanning-too-frequently failure with the remaining retry delay.
  • Use the greater of the default cooldown and the reported retry delay for both retry admission and user feedback.
  • Show the active wait in a dedicated localized message, rounding fractional seconds upward so it is never understated.
  • Preserve the permission-specific and generic registration-failure messages.
  • Prevent an older cooldown job from clearing a newer cooldown.
  • Keep JVM and iOS implementations as no-ops.

Testing

Added or updated coverage for:

  • retry cancellation after profile replacement, remote disconnect, and authority revocation during backoff;
  • normal retries while the captured session remains active;
  • one bounded bonded-device scan, fresh-advertisement selection, and bonded fallback after a miss;
  • process-wide quota sharing, rolling-window expiration, and concurrent reservations;
  • exactly-once scan reservation before advertisement collection, with expired requests consuming no quota;
  • native scanning-too-frequently error mapping, effective retry cooldowns, and localized rounded-up wait messages;
  • stale cooldown-job protection;
  • direct limiter coverage independent of scanner behavior tests.

The exact patch-equivalent source branch passed Android builds, lint and KMP smoke compilation, screenshot validation, all test shards, and the aggregate workflow check.

On-device F-Droid validation exercised repeated BLE-to-TCP and TCP-to-BLE switching. It covered both bonded reconnect outcomes:

  • a bounded scan miss followed by successful bonded-handle fallback;
  • a fresh advertisement followed by a successful scanned-device connection.

BLE profile setup and mesh handshakes completed without Android scan-start rejection, stale database association, Room/SQLite failure, process crash, ANR, or OOM. Delayed callbacks and node work from replaced transport generations were rejected rather than applied to the active session.

Migration Notes

  • No user data migration is required.
  • Existing BLE addresses and OS bonds are unchanged.
  • Bonding and pairing behavior are unchanged.
  • GATT connection timeouts and the outer reconnect/backoff policy are unchanged.
  • Non-bonded discovery and non-Android scan behavior are unchanged.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added Android BLE scan-start rate limiting to reduce excessive scan requests.
    • Enhanced “scanning too frequently” handling by including a retry wait time to support smarter retry behavior and cooldowns.
  • Bug Fixes
    • Improved user-facing error classification and messaging for scan quota/rate-limit failures.
    • Updated bonded-device BLE refresh to use a single bounded scan window and improved write retry stopping when a session is retired.
    • Added localization support for the “too frequent” retry message.

Give BLE retry callers an explicit authority predicate that is checked after each failure and again after backoff. The initial attempt remains unconditional, cancellation still propagates immediately, and revocation rethrows the exact latest operation failure without consuming another attempt.

Bind queued radio writes to the Meshtastic profile captured under the write mutex. When disconnect or reconnect replaces that profile, the old write stops retrying and cannot tear down the replacement session; its final stale failure is reduced to debug-level diagnostics.

Add deterministic retry-revocation and remote-disconnect coverage, plus fake write-attempt accounting that includes failures occurring before a write is recorded.
Bonded reconnects previously registered a two-second targeted scan and then a separate five-second scan when the first window missed. Reconnect retries, Connections-screen discovery, and rapid device switching could therefore exceed Android's BLE scan-start quota and stall connection progress behind the platform cooldown.

Use one five-second address-filtered scan for the bonded path. Return immediately when a fresh advertisement arrives, prefer that fresh device over the stale bonded handle, and retain the bounded autoConnect fallback when the scan misses. Leave the non-bonded retry policy unchanged.

Update deterministic reconnect coverage to require exactly one scanner registration for both fresh-device discovery and bonded-handle fallback, including the selected-address and service filters and the bounded scan duration.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 54f40072-f0fc-4d65-ac1d-bdbec02f5900

📥 Commits

Reviewing files that changed from the base of the PR and between 53b9945 and 7eba669.

📒 Files selected for processing (13)
  • .skills/compose-ui/strings-index.txt
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • core/resources/src/commonMain/kotlin/org/meshtastic/core/resources/GetString.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • .skills/compose-ui/strings-index.txt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt

📝 Walkthrough

Walkthrough

Adds Android BLE scan-start rate limiting with rolling-window reservations, retry-after propagation, scanner failure classification, retry cancellation on stale sessions, single bounded bonded-device scans, and cooldown behavior in ScannerViewModel.

Changes

BLE lifecycle and scan control

Layer / File(s) Summary
Scan-start quota contract and Android implementation
core/ble/src/commonMain/..., core/ble/src/androidMain/..., core/ble/src/androidHostTest/..., core/ble/src/iosMain/..., core/ble/src/jvmMain/...
Defines the limiter abstraction, adds Android rolling-window quota enforcement with a process-wide instance, provides no-op iOS/JVM implementations, and tests quota, expiry, and concurrency behavior.
Scanner reservation and failure classification
core/ble/src/commonMain/..., core/ble/src/commonTest/...
Reserves scan starts before advertisement collection, recognizes Android “scanning too frequently” failures, and adds scanner integration tests.
Retry authority and stale session handling
core/ble/src/commonMain/..., core/testing/src/commonMain/..., core/network/src/commonMain/..., core/ble/src/commonTest/..., core/network/src/commonTest/...
Adds retry predicates, stops writes after session replacement, tracks write attempts, and validates retry termination.
Single bounded bonded-device scan
core/network/src/commonMain/..., core/network/src/commonTest/...
Uses one bounded address-filtered scan for bonded discovery and updates reconnect timing and fallback assertions.
Retry-after-aware scanner cooldown
feature/connections/src/commonMain/..., feature/connections/src/commonTest/..., core/resources/..., .skills/compose-ui/...
Applies exception-provided retry delays, protects cooldown generations, expands failure messages, and tests cooldown timing.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ScannerViewModel
  participant KableBleScanner
  participant AndroidBleScanStartLimiter
  ScannerViewModel->>KableBleScanner: start scan
  KableBleScanner->>AndroidBleScanStartLimiter: reserveStart()
  AndroidBleScanStartLimiter-->>KableBleScanner: success or retryAfter failure
  KableBleScanner-->>ScannerViewModel: scan result or BleScanStartException
  ScannerViewModel->>ScannerViewModel: apply retry-aware cooldown
Loading

Possibly related PRs

Suggested reviewers: jamesarich

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main BLE reconnect and Android scan-lifecycle hardening in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bugfix PR tag label Jul 23, 2026
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 23, 2026 12:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt (1)

69-79: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the reservation test assert ordering, not just invocation count.

emptyFlow() has no observable collection side effect, so this test passes even if reserveScanStart() runs after advertisements(...). Record reservation and advertisement-collection events, then assert reservation occurs first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt`
around lines 69 - 79, Update the test `scan reserves one platform start before
collecting advertisements` to record both the scan-start reservation and
advertisement collection as ordered events. Configure the scanner’s empty
scan-results flow to append an advertisement-collection event when collected,
append the reservation event in `BleScanStartLimiter`, and assert the
reservation event precedes advertisement collection while retaining the
single-reservation assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt`:
- Around line 37-38: Update the public BleScanStartException class to add an
explicit two-argument constructor accepting reason and cause, delegating to the
existing constructor with retryAfter unset; preserve the current three-argument
constructor and its default-parameter behavior for source compatibility.

---

Nitpick comments:
In
`@core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt`:
- Around line 69-79: Update the test `scan reserves one platform start before
collecting advertisements` to record both the scan-start reservation and
advertisement collection as ordered events. Configure the scanner’s empty
scan-results flow to append an advertisement-collection event when collected,
append the reservation event in `BleScanStartLimiter`, and assert the
reservation event precedes advertisement collection while retaining the
single-reservation assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aff6a4d0-c5b1-4d20-b23a-a6b11e221f0b

📥 Commits

Reviewing files that changed from the base of the PR and between bb7508a and 8430924.

📒 Files selected for processing (16)
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleRetry.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/BleRetryTest.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • core/network/src/commonMain/kotlin/org/meshtastic/core/network/radio/BleRadioTransport.kt
  • core/network/src/commonTest/kotlin/org/meshtastic/core/network/radio/BleRadioTransportReconnectCrashTest.kt
  • core/network/src/commonTest/kotlin/org/meshtastic/core/network/radio/BleRadioTransportTest.kt
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeBle.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt

@jeremiah-k
jeremiah-k marked this pull request as draft July 23, 2026 12:38
@jeremiah-k
jeremiah-k force-pushed the bugfix/ble-reconnect-lifecycle branch from 8430924 to 1e891fb Compare July 23, 2026 12:49
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 23, 2026 13:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt`:
- Around line 396-412: The ScanningTooFrequently branch in the exception.reason
mapping must provide a localized quota-specific message that includes
exception.retryAfter rounded up to the required time unit. Add or reuse the
appropriate resource and pass the formatted remaining cooldown through
getStringSuspend, while preserving the existing generic message for
ApplicationRegistrationFailed and the permission message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c04ddfe-2a55-451d-81c3-dc63793fe9ec

📥 Commits

Reviewing files that changed from the base of the PR and between 8430924 and 1e891fb.

📒 Files selected for processing (10)
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt
🚧 Files skipped from review as they are similar to previous changes (8)
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt

@jeremiah-k
jeremiah-k marked this pull request as draft July 23, 2026 13:17
@jeremiah-k
jeremiah-k force-pushed the bugfix/ble-reconnect-lifecycle branch from 1e891fb to 53b9945 Compare July 23, 2026 13:26
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 23, 2026 14:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt`:
- Around line 80-81: Move reserveScanStart() into the withTimeoutOrNull(timeout)
block in KableBleScanner so the timeout covers both reservation and
advertisement collection. Preserve the existing scan behavior while ensuring
zero or expired timeouts cannot consume a reservation slot.

In `@core/resources/src/commonMain/composeResources/values/strings.xml`:
- Line 146: Replace the bluetooth_scan_too_frequent string resource with a
plurals resource containing grammatically correct one and other forms, then
update the caller that formats this cooldown message to use the plural-resource
API with the remaining seconds value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 15f4be60-454e-4646-96f4-93c16ed09ab1

📥 Commits

Reviewing files that changed from the base of the PR and between 1e891fb and 53b9945.

📒 Files selected for processing (12)
  • .skills/compose-ui/strings-index.txt
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt
🚧 Files skipped from review as they are similar to previous changes (9)
  • core/ble/src/jvmMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.jvm.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.kt
  • core/ble/src/iosMain/kotlin/org/meshtastic/core/ble/BleScanStartLimiter.ios.kt
  • core/ble/src/commonTest/kotlin/org/meshtastic/core/ble/KableBleConnectionTest.kt
  • feature/connections/src/commonTest/kotlin/org/meshtastic/feature/connections/ScannerViewModelTest.kt
  • core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleScanStartException.kt
  • core/ble/src/androidMain/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiter.kt
  • core/ble/src/androidHostTest/kotlin/org/meshtastic/core/ble/AndroidBleScanStartLimiterTest.kt
  • feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt

Comment thread core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt Outdated
Comment thread core/resources/src/commonMain/composeResources/values/strings.xml Outdated
@jeremiah-k
jeremiah-k marked this pull request as draft July 23, 2026 14:08
Android applies its BLE scan-start quota across the entire process, while reconnect discovery and Connections-screen discovery previously tracked retries independently. Their combined activity could register a sixth scan inside thirty seconds, after which some platform versions report status 6 and others leave the scan without a useful callback.

Reserve native scan starts through one process-wide, mutex-protected rolling window before Kable reaches BluetoothLeScanner. Keep reservation inside the caller timeout so expired requests consume no quota and limiter contention cannot outlive the requested scan. Surface quota exhaustion as a typed failure with the remaining retry delay, retain narrow wrapping for platform-reported failures, and keep JVM and iOS behavior unchanged through no-op implementations.

Keep scanner unit tests independent from Android process state through an overridable internal reservation seam. Assert that each production scan reserves exactly once and before advertisement collection, while zero-duration requests do not reserve; dedicated Android limiter tests retain ownership of singleton sharing, exhaustion, rolling expiry, and concurrent admission.

Use one effective cooldown for retry admission and a dedicated localized plural message. Round displayed seconds upward so the UI never understates the enforced wait, while preserving permission-specific and generic registration-failure messages. Cover native error mapping, cooldown behavior, and fractional-duration formatting.
@jeremiah-k
jeremiah-k force-pushed the bugfix/ble-reconnect-lifecycle branch from 53b9945 to 7eba669 Compare July 23, 2026 14:15
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 23, 2026 14:44
@jamesarich
jamesarich added this pull request to the merge queue Jul 23, 2026
Merged via the queue into meshtastic:main with commit 112c8b0 Jul 23, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants