8000 fix(cbio): keep the authenticated harness from stranding its GATT client by shroominic · Pull Request #204 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content

fix(cbio): keep the authenticated harness from stranding its GATT client - #204

Closed
shroominic wants to merge 1 commit into
fix/cbio-gs1-evidence-permissionsfrom
fix/cbio-gs1-harness-lifecycle
Closed

shroominic wants to merge 1 commit into
fix/cbio-gs1-evidence-permissionsfrom
fix/cbio-gs1-harness-lifecycle

Conversation

@shroominic
Copy link
Copy Markdown
Owner

Closes #200. Base: #202.

Two consecutive device runs recorded targetAcquired: true with notifications: 0 and writes: {}, and the third added disconnect_failed plus a failed gatt_not_released invariant. The failure was after acquisition and it cascaded.

The harness wrapped every FlutterBluePlus call in an outer Dart timeout. In flutter_blue_plus 2.2.1 connect, disconnect, and discoverServices each hold _MutexFactory.getMutexForKey("global") for the life of their plugin future (write and setNotifyValue do too), and they own their own timeouts. An outer timeout therefore does not cancel the operation — it abandons the future that holds the mutex. The next disconnect() blocked on mtx.take() until its own outer timeout fired, gattReleased stayed false, and the GATT client was left registered for the following run.

This is the same class as #143, and the app transport already avoids it (disconnectWithPluginTimeout, writeWithPluginTimeout, setNotifyWithPluginTimeout, connectWithScanStoppedRetry). The harness now uses that proven shape:

  • plugin-owned timeouts on connect / disconnect / discover / subscribe / write;
  • the scanner is stopped before connecting, and one Android status-133 connect is retried;
  • mtu: null on connect, requestMtu(247) after discovery, and a refused negotiation is logged as mtu_failed instead of ending the session — matching the app session;
  • gattReleased is reported only after the disconnect completed with the device actually disconnected, and a link that never came up reports disconnect-skip not-connected rather than a false success or a false failure.

No assertion is relaxed: the allowed-write set, the closed error taxonomy, and every invariant in CbioSessionEvidence are unchanged.

Device evidence, from a clean install with #202 in place:

evidence/gs1-session-20260918T055737-cbio_glucose_authenticated_test.json
outcome=completed gattReleased=true notifications=126
writes=authentication 1, clock_set 1, glucose_read 1, raw_history_read 1
errors=none unitStatus=unverified

Two consecutive device runs on 2026-09-18 showed `targetAcquired: true` with
`notifications: 0` and `writes: {}`, then a third added `disconnect_failed` and
`gatt_not_released`. The failure was after acquisition, and it cascaded.

The harness wrapped every FlutterBluePlus call in an outer Dart timeout.
`connect`, `disconnect`, `discoverServices`, `setNotifyValue`, and a
with-response `write` each hold `flutter_blue_plus`'s global Bluetooth mutex for
as long as their plugin future is pending, so an outer timeout did not cancel
the operation: it abandoned the future that owned the mutex. The following
`disconnect()` then blocked on that mutex until its own outer timeout fired, so
the link was reported unreleased and left up, and the next run connected into a
client the sensor still had.

The plugin owns those timeouts. The harness now passes them through, stops the
scanner before connecting, retries one Android status-133 connect the way the
app transport does, tolerates a refused ATT MTU negotiation the way the app
session does, and reports `gattReleased` only after the disconnect finished with
the device actually disconnected.

Refs #200
@shroominic
Copy link
Copy Markdown
Owner Author

Attribution note from the A/B I ran while landing this, so the record is accurate.

With the app's BLE permissions granted, the unmodified harness (e856f16, no changes from this stack) also completed a full session: outcome=completed notifications=125 glucose=1782 raw=1504 disconnect-ok. So #199 is the root cause of the failure class, and this PR is not what makes a device session complete.

What this PR does is remove the failure modes that turned a bad device state into a wedged lane: the stranded GATT client and the unowned plugin futures. Those are the exact shapes recorded in gs1-session-20260918T051210-...-1.json and gs1-session-20260918T051312-...json (notifications: 0, writes: {}, then disconnect_failed + gatt_not_released), and they match flutter_blue_plus 2.2.1's global-mutex behaviour. It is a robustness fix with a code-provable mechanism, not the thing that unblocked the session.

@shroominic
Copy link
Copy Markdown
Owner Author

Not absorbed by the GS1 consolidation (#209, feature/cbio-gs1). Left open on purpose: the GATT lifecycle work (the attempt bookkeeping and the bounded wait around the authenticated harness) is not in the consolidated openhealth/integration_test/cbio_glucose_authenticated_test.dart. Owner: device-evidence harness lane.

@shroominic
Copy link
Copy Markdown
Owner Author

Carried into #209 (head be9b47a) as 65a927b — the same change, applied to the consolidated branch.

The harness no longer wraps plugin calls in a Dart .timeout() the plugin keeps owning after the future is abandoned (that abandoned future holds the plugin's global Bluetooth mutex), an ignored ATT MTU exchange is treated as "keep the default" rather than a failed session, and teardown goes through _releaseLink / _stopRadio so the GATT client is never left stranded. Without this the on-device session that produced the evidence artifacts does not complete.

Closing as carried. Carrying PR: #209.

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