fitermay
07/27/2022, 8:30 PMJoffrey
07/27/2022, 9:25 PMtry/finally
block to clean it up in every scenario (normal completion and exceptions)fitermay
07/27/2022, 9:53 PMZach Klippenstein (he/him) [MOD]
07/27/2022, 11:02 PMIt cannot happen when returning from one suspend function to another?No. For example, if you run this `foo`:
suspend fun foo() {
val job = coroutineContext.job
println("In foo, calling bar (isActive=${job.isActive})")
bar()
println("In foo, after bar returned (isActive=${job.isActive})")
}
suspend fun bar() {
println("In bar, cancelling job…")
coroutineContext.job.cancel()
println("In bar, job cancelled, returning to foo…")
}
You’ll get this sequence of messages:
In foo, calling bar (isActive=true)
In bar, cancelling job…
In bar, job cancelled, returning to foo…
In foo, after bar returned (isActive=false)
uli
07/27/2022, 11:18 PMsuspend fun allocateResource() = withContext(<http://Dispatchers.io|Dispatchers.io>) {
// allocate resource. E.g. open a database
return resource
}
suspend fun leakResource() {
val ressource = allocateResource()
try {
doSomething(resource)
} finally {
ressource.close()
}
}
withContext
would catch the cancelation before returning and leak the resource even before it is returned from allocateRessource
. Nothing much you can do about it.ephemient
07/27/2022, 11:18 PMensureActive()
or runInterruptible {}
to allow for cancellation in the middle
2. you should not make suspend calls or launch new coroutines in a finally
(or catch (Exception)
block if it could contain CancellationException
) because the containing job may be cancelled which would cause such calls to immediately propagate cancellation insteadwithContext(NonCancellable)
if you need to ensure cancellation doesn't happen in the midst of a certain blockuli
07/27/2022, 11:23 PMephemient
07/27/2022, 11:34 PMvar resource: Resource? = null
try {
withContext(NonCancellable) {
resource = allocateResource()
}
doSomething(resource)
} finally {
resource?.close()
}
uli
07/27/2022, 11:39 PMfitermay
07/27/2022, 11:43 PMephemient
07/27/2022, 11:49 PMfitermay
07/27/2022, 11:59 PMephemient
07/28/2022, 12:00 AMfitermay
07/28/2022, 12:01 AMephemient
07/28/2022, 12:01 AMuli
07/28/2022, 12:01 AMfitermay
07/28/2022, 12:01 AMuli
07/28/2022, 12:01 AMephemient
07/28/2022, 12:01 AMfitermay
07/28/2022, 12:01 AMuli
07/28/2022, 12:18 AMfitermay
07/28/2022, 12:22 AMuli
07/28/2022, 12:22 AMsuspend*Coroutine
are called. I like to call these the ‘leaf’ suspend functions. All other suspend functions are only suspend when the functions they call suspend.
I.e. you build up a ‘real’ call stack until you run into a `suspend*Coroutine`call or alike.fitermay
07/28/2022, 12:23 AMephemient
07/28/2022, 12:24 AMuli
07/28/2022, 12:24 AMsuspend fun foo() : Int {
println("Before suspend")
delay(1)
println("After suspend")
return 1
}
All the magic happens around delay
. entering foo and exiting does not suspend and does not resumeimport kotlinx.coroutines.Job
import kotlinx.coroutines.delay
val job = Job()
suspend fun bar(): Int {
println("Before suspemd")
delay(10)
println("After suspemd")
return 1
}
suspend fun foo() {
println("Before bar")
var i = bar()
println("After bar")
}
ephemient
07/28/2022, 12:25 AMuli
07/28/2022, 12:25 AMdelay
is called, bar returns with COROUTINE_SUSPENDED. This is used to signal that bar returned without a result and will return again, later. foo will then also return COROUTINE_SUSPENDED all in all unwrapping the whole call stack.fitermay
07/28/2022, 12:33 AMuli
07/28/2022, 12:40 AMsuspend fun foo() = withContext {
It is a common pattern and hides a resume and thereby a cancellation point after the inner returnephemient
07/28/2022, 12:56 AMfun allocate(): Resource {
val future = executor.submit { ... }
return future.get()
}
which can also leak if the current thread is interruptedfitermay
07/28/2022, 1:48 AMephemient
07/28/2022, 2:08 AMfitermay
07/28/2022, 4:39 AM