8000 fix(ble): close the FlutterBluePlus scan stream before cleanup can block it by shroominic · Pull Request #144 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content

fix(ble): close the FlutterBluePlus scan stream before cleanup can block it - #144

Closed
shroominic wants to merge 1 commit into
feature/cbio-gs1-glucose-livefrom
fix/ble-scan-stream-close
Closed

shroominic wants to merge 1 commit into
feature/cbio-gs1-glucose-livefrom
fix/ble-scan-stream-close

Conversation

@shroominic
Copy link
Copy Markdown
Owner

Fixes #143.

The defect

FlutterBluePlusTransport.scan handed callers a stream that outlived the scan.
When flutter_blue_plus stopped the radio at its own timeout, the transport
called closeStreamSafely(stopScan: false) → closeFlutterBluePlusScanResources,
which awaited cancelResults, cancelScanning, awaitPendingStart, and
stopScan before closeController. Any of those plugin futures staying
pending on a real stack means controller.close() never runs: the caller waits
forever, and cancelling the subscription hangs too.

Every app scan goes through this transport, so the nearby-sensor list sat on
"Looking for supported sensors nearby…" indefinitely and no driver was ever
selectable — even with the sensor advertising on the same phone's Bluetooth page.

Measured before the fix (Pixel 6, 6 s scan window)

Signal Observed
FlutterBluePlus.isScanningNow false at ~7 s
transport stream still open at 12 s
.timeout(25 s) on the stream fired
plugin's raw onScanResults 429 advertisements in the same window

The radio and the plugin were healthy; only this wrapper was wedged.

The fix

  • closeFlutterBluePlusScanResources closes the caller's stream first, then
    runs the plugin cancellations, each bounded by stepTimeout (default 5 s).
  • scan arms a deadline for timeout + 750 ms so a scan closes even when the
    plugin stops the radio without publishing isScanning = false.
  • The two existing ordering tests are updated to the new order: they asserted
    the old sequence that caused the hang.

Regression test

flutter_blue_plus_scan_close_test.dart drives the cleanup primitive with
futures that never complete and asserts the stream still closes and cleanup
still returns. It fails on the previous ordering and passes now.

Verification

  • packages/cgm_ble_flutter: flutter analyze clean, flutter test 27 passed.
  • Device run on the Pixel 6 after the fix: the app's own scan lists the sensor
    (1 sensor found) and connects; see the app-integration PR for the screen
    evidence.

This branch is based on feature/cbio-gs1-glucose-live, the branch that carries
the current FlutterBluePlusTransport; the equivalent fix does not exist on
main yet.

FlutterBluePlusTransport.scan returned a stream that outlived the scan.
flutter_blue_plus stops the radio at its own timeout, but the transport's
cleanup awaited cancelResults, cancelScanning, awaitPendingStart, and
stopScan before closeController. On a stack that leaves any of those
pending, controller.close() never ran, so callers waited forever and
cancelling the subscription hung for the same reason.

- close the caller's stream first in closeFlutterBluePlusScanResources,
- bound each cleanup step so a pending plugin future cannot hold a caller,
- arm a scan deadline so a scan still closes when the plugin stops the
  radio without publishing isScanning = false,
- cover it with a regression test that drives cleanup with futures that
  never complete.

Measured before the fix on a Pixel 6: a 6 s window left isScanningNow
false at ~7 s while the transport stream was still open at 12 s, and a
25 s timeout on the stream fired, while the plugin's raw result stream
delivered 429 advertisements in the same period.

Fixes #143.
@shroominic
Copy link
Copy Markdown
Owner Author

Cross-lane note from the device-session lane: this PR's scan shape is now mirrored by the device harness, so the two paths stay comparable.

openhealth/integration_test/cbio_glucose_authenticated_test.dart was acquiring its target with a single FF30-filtered scan and trusting startScan plus the plugin's own stop signal for its bounds. It now does what this transport already does: subscribe before starting the radio, own a hard pass deadline, end the pass on either signal, close the scan before plugin cleanup that can block, and keep the FF30 classification so a foreign advertiser is never surfaced as a sensor. The harness also follows cbio_driver.dart's filtered-then-unfiltered retry, which is what #200/#201/#205 record.

No file this PR owns was touched. Nothing here needs a change on this branch; this is only so the next reviewer of cgm_ble_flutter knows the harness is now a second consumer of the same shape.

@shroominic
Copy link
Copy Markdown
Owner Author

Carried by the GS1 consolidation in PR #209 (branch feature/cbio-gs1, head 3eca4f4).

This PR is contained in the consolidated line; the merge that carries it is c85f827.

Closing as subsumed by the consolidation - not as won't fix. The per-defect evidence stays in this thread, and the carrying commit above is where the change now lives.

@shroominic shroominic closed this Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0