Skip to content

darwin: return an error when Connect fails - #519

Open
acouvreur wants to merge 1 commit into
devfrom
darwin-connect-error
Open

acouvreur wants to merge 1 commit into
devfrom
darwin-connect-error

Conversation

@acouvreur

Copy link
Copy Markdown
Member

Fixes #278.

On macOS, Connect could return a zero Device with a nil error. The central manager delegate dropped the error from DidFailToConnectPeripheral and DidDisconnectPeripheral, and Connect returned connectionError, which is only set on its own timeout. The first GATT call on that Device then panicked.

This happens when you connect right after Disconnect. The disconnect event of the old connection reaches the new pending Connect, as described by jbanyer in the issue.

The delegate now sends the error with the peripheral. Connect returns, in order, its own timeout error, the CoreBluetooth error, or a generic error when CoreBluetooth gives none. A Connect unblocked by Adapter.Reset also returns an error now instead of a zero Device.

How to verify

Not tested on hardware yet. On a Mac, with any BLE peripheral in range:

  1. Connect, call DiscoverServices, call Disconnect, then call Connect again right away, as in the issue.
  2. Before this change the second Connect can return a nil error and DiscoverServices panics. With it, Connect either succeeds or returns an error, and a retry connects.
  3. Connect to a device that is switched off and check that Connect returns timeout on Connect.
Case Before After
Connect right after Disconnect nil error, zero Device, panic on use error
CoreBluetooth fails the connection nil error CoreBluetooth error
Timeout timeout on Connect timeout on Connect
Connect pending during Reset nil error error

The central manager delegate dropped the error from
DidFailToConnectPeripheral and DidDisconnectPeripheral. Connect then
returned a zero Device with a nil error, and the first GATT call on it
panicked.

Send the error along with the peripheral and return it from Connect.
Fall back to a generic error when CoreBluetooth gives none, which happens
when a disconnect from an earlier connection reaches a pending Connect.
@acouvreur
acouvreur marked this pull request as ready for review October 17, 2026 20:44
@deadprogram deadprogram added this to the 0.17.0 milestone Oct 27, 2026

@deadprogram deadprogram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this. The main fix is correct. Before this change Connect returned connectionError, which is nil unless the timeout fired.

Some points:

  1. When a stale disconnect gets to Connect and it returns an error, the a.cm.Connect(prphs[0], nil) call is still pending in CoreBluetooth. If that attempt then succeeds, DidConnectPeripheral finds no channel and ignores it. The OS link stays up and no Device owns it. Please call a.cm.CancelConnect(prphs[0]) on this error path when connectionError == nil.
  2. After this change, a stale disconnect still makes the new Connect fail, so the caller must retry. This is better than a panic, but please say it in the PR description.
  3. The Reset comment still has an em dash (Connect — closing). You change the next line, so please fix this one too.

Also please test on a Mac with the steps in the description before we merge.

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.

Immediately call adapter.Connect(addr) after device.Disconnect() will cause DiscoverServices() panic

2 participants