Skip to content

fix(win): stop Disconnect from freezing the UI, and a 100% CPU spin on link loss - #21

Open
ssuyash3000 wants to merge 1 commit into
AmitRajput-Dev:mainfrom
ssuyash3000:fix/windows-disconnect-hang
Open

ssuyash3000 wants to merge 1 commit into
AmitRajput-Dev:mainfrom
ssuyash3000:fix/windows-disconnect-hang

Conversation

@ssuyash3000

Copy link
Copy Markdown

Three related problems around teardown on Windows:

  1. BluetoothWrapper::disconnect() took _connectorMtx before closing the
    socket, but a worker thread holds that same lock while blocked in recv().
    Closing the socket is what releases that thread, so the UI thread waited on
    the lock — Disconnect froze the window until the 2.5s recv timeout expired,
    and closing the app while a read was in flight hung it. On Windows, close
    first, then take the lock; other platforms keep the old order.

  2. _socket was written by the UI thread (disconnect) while a worker thread
    read it inside send()/recv() — a data race that could hand recv() a
    closed or recycled handle. It is now std::atomic<SOCKET>, swapped through
    a single _closeSocket().

  3. recv() returning 0 (graceful close by the headphones) was treated as a
    successful read of an empty buffer, so BluetoothWrapper::_readMessage()
    looped forever looking for a start marker — one core pegged until the app
    was killed. Both the connector and the wrapper now treat it as a
    disconnect.

Also loops send() until the whole frame is out: a partial send left the
device waiting for the rest of a command that never arrived.

…n link loss

Three related problems around teardown on Windows:

1. `BluetoothWrapper::disconnect()` took `_connectorMtx` before closing the
   socket, but a worker thread holds that same lock while blocked in `recv()`.
   Closing the socket is what releases that thread, so the UI thread waited on
   the lock — Disconnect froze the window until the 2.5s recv timeout expired,
   and closing the app while a read was in flight hung it. On Windows, close
   first, then take the lock; other platforms keep the old order.

2. `_socket` was written by the UI thread (disconnect) while a worker thread
   read it inside `send()`/`recv()` — a data race that could hand `recv()` a
   closed or recycled handle. It is now `std::atomic<SOCKET>`, swapped through
   a single `_closeSocket()`.

3. `recv()` returning 0 (graceful close by the headphones) was treated as a
   successful read of an empty buffer, so `BluetoothWrapper::_readMessage()`
   looped forever looking for a start marker — one core pegged until the app
   was killed. Both the connector and the wrapper now treat it as a
   disconnect.

Also loops `send()` until the whole frame is out: a partial send left the
device waiting for the rest of a command that never arrived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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