JuulLabs / kable

Kotlin Asynchronous Bluetooth Low-Energy
https://juullabs.github.io/kable
Apache License 2.0
852 stars 85 forks source link

Worker never ends if I use kable in it #567

Closed solvek closed 1 month ago

solvek commented 1 year ago

If I just create a periferal instance inside worker then this worker never ends.

I am enqueueing a unique work. For the first time it runs fine (I see in logs Finished worker), when I enqueue again the work it does not start. I assume this is because the previous work was not finished.


@HiltWorker
class DfuWorker @AssistedInject constructor(@Assisted private val context: Context,
                                            @Assisted workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
    override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
        Timber.tag(TAG).d("Started")

        // If I comment this line all works as expected
        val peripheral = peripheral(Biosensors.bluetoothDevice(deviceId))

        Timber.tag(TAG).d("Finished worker")
        Result.success()
    }

    companion object {
        private const val TAG = "DfuWorker"

        fun WorkManager.runDfuWorker(deviceId: String, deviceType: DeviceType){
            val data = Data
                .Builder()
                .putString(DATA_DEVICE_ID, deviceId)
                .putInt(DATA_DEVICE_TYPE, deviceType.code)
                .build()

            val workRequest = OneTimeWorkRequest
                .Builder(DfuWorker::class.java)
                .addTag("FIRMWARE_UPDATE")
                .setInputData(data)
                .build()

            enqueueUniqueWork(
                "update_firmware",
                ExistingWorkPolicy.KEEP,
                workRequest
            )

            Timber.tag(TAG).d("Requested enqueue worker for dfu.")
        }
    }
}
solvek commented 1 year ago

I have the most recent version 0.27.0

twyatt commented 1 year ago

This is likely a duplicate of #378.

I've been slowly integrating changes in Kable to address the issue, it will be some time before it can be completely fixed.

In the mean time, one possible workaround is to create a parent Job for your peripheral that you explicitly control its lifecycle, for example:

override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
    val scope = CoroutineScope(Job(coroutineContext.job))
    try {
        val peripheral = scope.peripheral(..)
    } finally {
        scope.cancel()
    }
}
twyatt commented 1 year ago

I'm hoping to get changes into 0.29.0 that would remove the need for this workaround.

twyatt commented 7 months ago

I'm hoping to get changes into 0.29.0 that would remove the need for this workaround.

Unfortunately lots of other things came up and this didn't happen. It'll likely be a bit more effort than originally anticipated, so it will be a number of version from now before this is fully resolved. #612 is a small step in the direction needed to fully resolve this, but I haven't had time to test it enough.

I'll try to get back to this when I can, but being that there is a workaround, it might not get prioritized very high vs. some other bugs. Thanks for being patient!