[Bug]: Unsubscription from characteristic notifications might create unstable state
TimofeyBurak opened this issue · comments
Component/Nuget
BluetoothLE Client (Shiny.BluetoothLE)
What operating system(s) are effected?
- iOS (13+ supported)
- Mac Catalyst
- Android (8+ supported)
- Windows (.NET 7 Target - only Core is currently supported, BLE is coming)
Version(s) of Operation Systems
Android 8.0
Hosting Model
- MAUI
- Native/Classic Xamarin
- Manual
Steps To Reproduce
- Connect to peripheral
- Subscribe to notifications by calling
NotifyCharacteristic(serviceUuid, chracteristicUuid)
- Unsubscribe from notifications while notifications are being enabled (in the middle of the async
WriteDescriptor()
execution) - Read (should also work with wright) any characteristic right after unsubscription
Expected Behavior
Next operaation after unsubscription should be executed succesfully.
Also, WriteDescriptor()
operation, which enables the notifications, should run into completion. Then, since there are no subscribers, another WriteDescriptor()
operation should be executed to disable the notifications.
NOTE: this expectation is based on general guidelines for working with BLE on Android, for example:
- [Tip 2: Don’t perform BLE operations in rapid-fire succession] https://punchthrough.com/android-ble-development-tips/
- [You can only do 1 asynchronous operation at a time.] https://medium.com/@martijn.van.welie/making-android-ble-work-part-3-117d3a8aee23
Actual Behavior
Shiny.BluetoothLE.BleException: Failed to read characteristic
is thrown.
According to the NotifyCharacteristic(serviceUuid, chracteristicUuid)
source code, async WriteDescriptor(descriptor, data, ct)
will be cancelled if observable is unsubscribed in the middle of execution. According to the WriteDescriptor(descriptor, data, ct)
source code, if cancelled - this method cancels the waiting for callback and throws exception.
So if cancellation of the WriteDescriptor(descriptor, data, ct)
happens, then:
- Awaiting for the descriptor write callback will be cancelled, but Android OS will continue execution (and might be completed successfully);
this.TryNotificationCleanup(characteristic, serviceUuid, characteristicUuid)
will not be executed, ascharacteristic
variable is not initialised at this point;SemaphoreOperationQueue
will be released, so the next operation in the queue would be executed right away (if there are any waiting), or new operation could be scheduled right away. Either way, new operation could be executed at the same time as Android OS is still executing descriptor write.
Exception or Log output
Shiny.BluetoothLE.BleException: Failed to read characteristic: 0000001f-0000-1000-8000-00805f9b34fb
at Shiny.BluetoothLE.Peripheral+<>c__DisplayClass35_1.b__1 (System.Threading.CancellationToken ct) [0x000d4] in <6b51d496651b43a9a8f0fce493c18768>:0
at Shiny.BluetoothLE.Intrastructure.OperationQueueExtensions+<>c__DisplayClass0_11[T].<QueueToObservable>b__1 () [0x0007c] in <6b51d496651b43a9a8f0fce493c18768>:0 at Shiny.BluetoothLE.Intrastructure.SemaphoreOperationQueue.Queue (System.Func
1[TResult] task, System.Threading.CancellationToken cancellationToken, System.String caller) [0x00157] in <6b51d496651b43a9a8f0fce493c18768>:0
at Shiny.BluetoothLE.Intrastructure.OperationQueueExtensions+<>c__DisplayClass0_0`1[T].b__0 (System.Threading.CancellationToken ct) [0x000cf] in <6b51d496651b43a9a8f0fce493c18768>:0
Code Sample
Due to NDA I cannot share the exact codebase we use in production, hope this simplified gist would be enough: https://gist.github.com/TimofeyBurak/169e3fb43d6741b998b4a64dfa678011
Code of Conduct
- I have supplied a reproducible sample that is NOT FROM THE SHINY SAMPLES!
- I am a Sponsor OR I am using the LATEST stable/beta version from nuget (v3.0 stable - ALPHAS are not taking issues - Sponsors can still send v2 issues)
- I am Sponsor OR My GitHub account is 30+ days old
- I understand that if I am checking these boxes and I am not actually following what they are saying, I will be removed from this repository!
It's a bit of a different use-case, but I can see how this can occur. I can work with the sample you've provided - thanks
I have reordered a couple of things around, but I'm only going to go so far with this one since it is a very weird use-case. Notifications aren't meant to hook/unhook in a rapid fashion like this.