CoreBluetooth didUpdateValueForCharacteristic callback is delayed

I'm experiencing weird behaviour with CoreBluetooth on iOS (testing on an iPhone 12 Pro with iOS 15.6.1).

My peripheral successfully requests a 15ms connection interval, and updates a characteristic before every connection interval. The app enables notifications on the characteristic.

Generally the didUpdateValueForCharacteristic callbacks are received at 15ms intervals as expected. However there's a weird pattern where every 10 seconds (exactly) there's a period of 250ms or so where the callbacks are disrupted, with gaps of 100ms between callbacks and then multiple calls in quick succession to "catch up".

I'd assumed it was just random interference or something until I noticed the 10 second pattern. I set up a BLE packet sniffer and captured a trace which shows the updates are all in fact transferred over-the-air successfully at the expected times, it is just on the iOS side where there is a delay in reporting them to the app.

Further digging with Instruments (thread state trace) revealed the CPU has plenty of idle time so it's not CPU starvation. I did notice a correlation with hci_rx and StackLoop threads in bluetoothd - they do a burst of activity every 10 seconds and this correlates exactly with the hiccups in the callbacks.

My application is pretty latency critical so it would be great to hear if anyone has experienced this before and any ideas for how to improve the situation. Ideally without needing to update the firmware of the peripheral but that is an option if it would help (ie moving to something other than GATT notifications to get the data across).

Replies

Here's an instruments screenshots showing the correlation with activity in those bluetoothd threads:

And a wider view that shows the 10 second pattern in this activity:

I've also posted this via feedback assistant as with the ID FB11469459

I'm still seeing this behaviour on iPhone 12 Pro on 16.0.2 and the 16.1 beta. I have noticed it's a bit device-specific though, and doesn't appear on earlier devices (iPod 7th Gen for example).

One other finding of interest - I wrote a quick iOS app for the peripheral side of things and it still reproduced there (with the peripheral running on iPod 7th Gen and central on iPhone 12 Pro). However I also implemented the L2CAP channel and noticed these updates did not seem to be disrupted in the same way.

I've made both the peripheral and central test apps (very rough and ready) public on GitHub:

https://github.com/tangobravo/ios-bluetooth-central

https://github.com/tangobravo/ios-bluetooth-peripheral

Here's an Instruments screenshot showing when the updates are received, when they are sent both via the channel and via an attribute update:

Note the gap in the characteristic updates still seems to correlate with bluetoothd activity looking at the CPU states data shown on the CPU tracks (for some reason the bluetoothd thread views are missing the data now unfortunately).

So that at least gives hope for a workaround - I'm investigating implementing the L2CAP approach on my actual peripheral now.