Having a suspend function fetchData()
. What it does is to launch a few jobs in the withContext
, so that it will only return after the jobs are complete (which are: suspend fun getData(): Boolean
).
And also want if it times out then return false from the function.
The problem is when it times out, with withTimeoutOrNull(500) { jobs.joinAll() }
, it stuck in the function not exit.
The log shows it times out, also clearly points to last line of the code before exit the function:
E/+++: +++ in fetchData() after null = withTimeoutOrNull(500), jobs.sizs: 3
E/+++: +++ --- exit fetchData(), allFresh: false
But the caller of the fetchData()
gets stuck and not return from the fetchData()
.
This is the caller:
suspend fun caller() {
var allGood = fetchData()
// never return to here
Log.e("+++", "+++ caller(), after allGood: $allGood = fetchData()")
...
}
Below is the code, how to cancel the jobs if timeout?
suspend fun fetchData(): Boolean = withContext(Dispatchers.IO) {
var allFresh = requestHandlertMap.size > 0
if (!allFresh) {
allFresh
} else {
val handlers = requestHandlertMap.values.toList()
val jobs: List<Deferred<Boolean>> = handlers.map {handler->
async(start = CoroutineStart.LAZY) {
if (isActive) handler.getData() else true
.also {
Log.e("+++", "+++ in fetchData():async{} after handler.getData()")
}
}
}
val result = withTimeoutOrNull(500) { jobs.joinAll() }
Log.e("+++", "+++ in fetchData() after $result = withTimeoutOrNull(500), jobs.size: ${jobs.size} ")
if (result != null) {
allFresh = jobs.all { deferred ->
deferred.await()
}
Log.e("+++", "+++ +++ +++ in fetchData() call onDataReady(), allFresh: $allFresh = deferred.await() ")
onDataReady()
} else {
// how to cancel the jobs ???
//jobs.all { deferred ->
//deferred.cancelChildren()
//}
allFresh = false
}
allFresh
.also {
Log.e("+++", "+++ --- exit fetchData(), allFresh: $allFresh ")
}
}
}
After some reading/trying, it seems having a few issues with the implementation.
CoroutineStart.LAZY
causes a strange behavior, that theasync(start = CoroutineStart.LAZY)
start sequentially (expecting they should start to be concurrently), so that when it times out it stuck in the function (guess because it is wrapped in thewithContext(Dispatchers.IO)
and not all child coroutines are completed -- if there is someone not start yet).Remove the
start = CoroutineStart.LAZY
makes it returning from thefun fetchData()
the
suspend fun getData(): Boolean
was not implementedcooperate to be cancellable
, which may causes it still stay in the function until all children are completed although timeout has already happened.seems it still need to call the
deferred.cancelChildren()
, otherwise they are not cancelled by thewithTimeoutNotNull()
, not sure why, isnt it supposed to cancel the jobs automatically?the change made
ref: here and here