Skip to content

fix(ble): park the bond-loop pause's standing connect once the link is down - #2802

Open
DX23876 wants to merge 1 commit into
ryanbr:mainfrom
DX23876:fix/bond-loop-pause-parks
Open

DX23876 wants to merge 1 commit into
ryanbr:mainfrom
DX23876:fix/bond-loop-pause-parks

Conversation

@DX23876

@DX23876 DX23876 commented Oct 16, 2026

Copy link
Copy Markdown

Refs #1539

What was wrong

The #1544 standing-connect escape never armed on Apple. Every site that trips the bond-loop pause runs inside didDisconnectPeripheral before that handler sets state.connected = false, and standingConnectWhilePausedIfDue read state.connected right there. The dying link still counted as up, so shouldStandingConnectWhilePaused refused and nothing was parked. The paused branch further down the same handler then refused too, because the trip had just started the 10-minute floor. With the app backgrounded nothing else re-arms it, so the strap stayed disconnected until the app was next opened, which is the symptom #1539 reported.

Seen on a WHOOP MG (fw 50.41.1.0): two connection timeouts within 11 s tripped the #617 detector, the log never printed "parking a standing connect", and the strap stayed disconnected for 49 minutes while a workout was being recorded, until the app was reopened.

Android never had this: its twin posts the check to the handler, so it runs after handleDisconnect has finished the teardown.

Change

Verification

  • BondLoopPauseParkingTests drives the real call order (trip while the link still reads connected, then tear down). It failed before the change and passes after it.
  • The wording is pinned with identical strings on both platforms (BondLoopPauseParkingTests, BondLoopPauseCauseTest).
  • macOS StrandTests and the Android com.noop.ble unit tests pass; the iOS target builds.
  • Hardware: the stranding was observed on an MG in a field log. The fixed build has not yet tripped the pause on a real strap, so the parked connect after a trip is verified by the test only.

…s down

Refs ryanbr#1539.

The ryanbr#1544 escape never armed on Apple. Every site that trips the pause runs
inside didDisconnectPeripheral before that handler sets state.connected to
false, and standingConnectWhilePausedIfDue read state.connected right
there: the dying link still counted as up, so shouldStandingConnectWhilePaused
refused and nothing was parked. The paused branch further down the same
handler then refused as well, because the trip had just started the 10-minute
floor. With the app backgrounded nothing else re-arms it, so the strap stayed
disconnected until the app was next opened, the symptom ryanbr#1539 reported.

Seen on a WHOOP MG: two connection timeouts within 11 s tripped the ryanbr#617
detector at 07:37:19, the log never printed "parking a standing connect", and
the strap stayed disconnected for 49 minutes until the app was reopened, while
a workout was being recorded.

Android never had this. Its twin posts the check to the handler, so it runs
after handleDisconnect has finished the teardown. The Swift side now does the
same on the next main-queue turn, which covers all four call sites at once.

The paused-disconnect line also said "strap keeps refusing to pair" for every
pause, including the ryanbr#617 drops and the ryanbr#971/ryanbr#982 pairing that never finished,
where no refusal was observed. Both platforms now record the cause with the
latch and name it: "strap keeps refusing to pair", "pairing never finished" or
"the link dropped within seconds of pairing N times in a row".

Tests: BondLoopPauseParkingTests drives the real call order (trip while the
link still reads connected, then tear down) and failed before the change; the
wording is pinned on both platforms with identical strings
(BondLoopPauseCauseTest). macOS StrandTests and the Android com.noop.ble unit
tests pass; the iOS target builds.
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