Marc
04/19/2024, 12:32 PMcoroutineScope {
select {
launch {
item.gatt.connectionState.first { it == GattConnectionState.STATE_DISCONNECTED }
}.onJoin { }
launch @SuppressLint("MissingPermission") {
while (isActive) {
try {
item.characteristic.splitWrite(item.data, writeType = BleWriteType.NO_RESPONSE)
break
} catch (e: Exception) {
delay(200.milliseconds)
}
}
}.onJoin { }
}
cancel()
}
My problem is that after one of the 2 select branches completes, the cancel()
call does nothing and the coroutineScope never completes, as it seems like the first()
call cannot be cancelled and the splitWrite()
call is also implemented using suspendCoroutine
and therefore also cannot be cancelled. How do I get around this issue?Zach Klippenstein (he/him) [MOD]
04/19/2024, 2:34 PMsuspendCancellableCoroutine
for the latter. For the former, is connectionState a flow?Marc
04/19/2024, 2:36 PMsplitWrite
implementation comes from a library, so I cannot really change it to use suspendCancellableCoroutine
sadly. Is there a way to work around this? And yes, connectionState
is a flow.Zach Klippenstein (he/him) [MOD]
04/19/2024, 2:52 PMsuspendCoroutine
does not provide any way for the coroutine to be notified of cancellation. You could launch the coroutine outside of structured concurrency, with a different parent job, and let it leak and hope it finishes on its own, but that’s pretty dangerous. I would submit a bug report to the library.Zach Klippenstein (he/him) [MOD]
04/19/2024, 2:52 PMfirst
wouldn’t cancel though. Is the flow suspending in a non-cancellable way somewhere upstream?Zach Klippenstein (he/him) [MOD]
04/19/2024, 2:57 PMselect
? It shouldn’t matter but trying to eliminate thingsMarc
04/19/2024, 2:58 PMconnectionState
property just exposes an internal MutableStateFlow
as a flow. I have had problems before with first
not being cancellable and I found a website claiming that cancellation is only being checked for every time the flow emits an item, so if it is stuck and you never get any new items, there is no way to cancel it. Is that true?Marc
04/19/2024, 2:58 PMDoes the cancellation issue repro if you don’t haveSo you mean I should try only awaiting one of the two branches?? It shouldn’t matter but trying to eliminate thingsselect
Zach Klippenstein (he/him) [MOD]
04/19/2024, 3:01 PMZach Klippenstein (he/him) [MOD]
04/19/2024, 3:04 PMfirst
and it just does a normal collect
call, so it should cancel fine as long as the upstream doesn’t block itZach Klippenstein (he/him) [MOD]
04/19/2024, 3:05 PMMutableStateFlow
didn’t allow cancellation , it would definitely be a bug if it didMarc
04/19/2024, 3:06 PMcoroutineScope {
launch {
item.gatt.connectionState.first { it == GattConnectionState.STATE_DISCONNECTED }
}.cancel()
}
and that does not get stuck. So cancelling outside of the select seems to work fine.Marc
04/19/2024, 3:07 PM.onJoin
instead of .onJoin { }
. I still don't know what the difference is, but the second one works and the first one just gets stuck forever...Zach Klippenstein (he/him) [MOD]
04/19/2024, 3:12 PMonJoin
doesn’t do anything, it just gives you a special thing that the `select`’s scope defines an invoke
function on. You were probably stuck because when the coroutine completed, you hadn’t actually ended up joining on it.Zach Klippenstein (he/him) [MOD]
04/19/2024, 3:13 PMMarc
04/19/2024, 3:14 PMjoin
on the jobs after I launch them for the select to do something?Zach Klippenstein (he/him) [MOD]
04/19/2024, 3:15 PMonJoin {}
will join on them. But that’s actually onJoin.invoke { }
. Just reading the onJoin
property won’t do anythingMarc
04/19/2024, 3:19 PMvar callbackCalled = false
val sendDataJob = launch @SuppressLint("MissingPermission") {
item.characteristic.splitWrite(item.data, writeType = BleWriteType.NO_RESPONSE)
callbackCalled = true
item.callback()
}
while (item.gatt.isConnected && !sendDataJob.isCompleted) {
delay(20.milliseconds)
}
if (item.gatt.isConnected) {
if (!callbackCalled) {
item.callback()
}
Napier.d { "Sent message from BLE message queue: ${item.data.value.toList()}" }
} else {
Napier.w { "Failed to send message from BLE message queue, device disconnected." }
}
I don't think that this is how you are supposed to do it, but at least it works for now. It kind of does what you suggested earlier with launching the job outside of structured concurrency.Zach Klippenstein (he/him) [MOD]
04/19/2024, 3:19 PMZach Klippenstein (he/him) [MOD]
04/19/2024, 3:20 PMMarc
04/19/2024, 3:21 PMZach Klippenstein (he/him) [MOD]
04/20/2024, 3:20 PMMarc
04/22/2024, 8:59 AM