NordicSemiconductor / Android-BLE-Library

A library that makes working with Bluetooth LE on Android a pleasure. Seriously.
BSD 3-Clause "New" or "Revised" License
1.98k stars 413 forks source link

No response from writeCharacteristic #552

Open Lightwood13 opened 4 months ago

Lightwood13 commented 4 months ago

Hi, I think I found a bug in writeCharacteristic method. If BLE device disconnects right before the call to writeCharacteristic, then it never returns success or failure.

Example to reproduce:

disconnect()
    .done { Log.d("test", "Disconnect request successful") }
    .fail { _, _ -> Log.d("test", "Disconnect request failed") }
    .enqueue()

writeCharacteristic(
    communicationCharacteristic,
    byteArrayOf(),
    WRITE_TYPE_DEFAULT
)
    .done { Log.d("test", "Write request successful") }
    .fail { _, _ -> Log.d("test", "Write request failed") }
    .invalid { Log.d("test", "Write request invalid") }
    .enqueue()

This example prints in the logs Disconnect request successful, but write request neither succeeds nor fails. I've also noticed the same behavior when BLE device disconnects because of poor signal and the code is trying to write characteristic at the same time.

WhenwriteCharacteristic is used from kotlin with .suspend() like this:

writeCharacteristic(
    communicationCharacteristic,
    data,
    WRITE_TYPE_DEFAULT
).suspend()

it just hangs, never returning or throwing anything. Also, because WriteRequest is not cancellable when suspended, this coroutine can't be cancelled, so wrapping it in withTimeout doesn't help.

philips77 commented 4 months ago

I'll have a look, thank you for creating the issue.

dees91 commented 2 months ago

@philips77 Have you had a chance to check this topic? I have similar problem, if writeCharacteristic is called after disconnect, none of the done,fail or invalid callbacks are called, what's more, the syncLock underneath is never released, blocking the thread indefinitely.

philips77 commented 2 months ago

I didn't have time, sorry.

32penkin commented 3 weeks ago

Hi guys, @Lightwood13 , @dees91 , have you found any solution for this issue?

Lightwood13 commented 3 weeks ago

@32penkin I'm using a workaround like this

suspend fun writeCharacteristic(
    characteristic: BluetoothGattCharacteristic,
    data: ByteArray,
    timeout: Duration
): Unit = withTimeoutOrNull(timeout) {
    suspendCancellableCoroutine { continuation ->
        writeCharacteristic(
            characteristic,
            data,
            WRITE_TYPE_DEFAULT
        )
            .done {
                if (!continuation.isCancelled) {
                    continuation.resume(Unit)
                }
            }
            .fail { _, status ->
                if (!continuation.isCancelled) {
                    continuation.resumeWithException(
                        BLERequestException("Write request failed with status $status")
                    )
                }
            }
            .enqueue()
    }
} ?: throw BLERequestException("Write request timed out")

class BLERequestException(message: String?) : Exception(message)

Here, instead of using library's .suspend() method of WriteRequest, that isn't cancellable, I'm manually suspending execution with suspendCancellableCoroutine and then call .enqueue() inside. If the write request doesn't succeed or fail within given timeout, the withTimeoutOrNull function will cancel the block inside it and return null, so we can handle this case. This method doesn't address the issue with lost resources and the thread that gets blocked indefinitely if request doesn't finish as @dees91 mentioned, but at least it allows to safely use writeCharacteristic from your code without it hanging.