What’s the best way to cleanup things that you cre...
# coroutines
a
What’s the best way to cleanup things that you create in a coroutine? I’m especially worried about removing created resources when being cancelled.
Copy code
myScope.launch {
    // If the job is canceled before createResourceSync() returns the resource is lost in transit
    val resource = withContext(Dispatchers.default) { createResourceSync() }
    try {
    } finally {
      resource.delete()
    }
}
d
May not be the best but...
Copy code
myScope.launch {
    // If the job is canceled before createResourceSync() returns the resource is lost in transit
    val resource: ResourceType
    try {
        resource = withContext(Dispatchers.Default) { createResourceSync() }
    } finally {
      resource.delete()
    }
}
I'm not sure if
resource
has to be a nullable var and
resource?.delete()
. Can't check right now.
a
That would lead to the same issue
d
How so?
delete
will be called.
a
If the job is cancelled the resource is lost
Because the coroutine wouldn’t be resumed
d
Hmm but it would be cancelled.
a
Yes, but if but a cancelled coroutine doesn’t resume
d
To my knowledge, cancel means resume with
CancellationException
or something.
a
It does, but instantly. It doesn’t wait for the child to complete
d
Oh, are you worried about what happens within
createResourceSync
?
a
Nope, since it’s sync it will always complete
Here is your example as a scratch file:
Copy code
runBlocking {
    val job = launch {
        var resource: String? = null
        try {
            resource = withContext(Dispatchers.Default) {
                Thread.sleep(100)
                "Hello"
            }
        } finally {
            println("Resource is $resource")
        }
    }
    delay(10)
    job.cancel()
}
d
How about,
Copy code
myScope.launch {
    // If the job is canceled before createResourceSync() returns the resource is lost in transit
    val resource: ResourceType
    try {
        withContext(Dispatchers.Default) {
            resource = createResourceSync()
        }
    } finally {
      resource.delete()
    }
}
a
It will print
"Resource is null"
Same thing, the exception is thrown instantly and the resource would not have been set yet
As I mentioned in the original issue the problem is solvable using atomic references but it’s complicated and error prone
d
It will be thrown by
withContext
but not within it. resource would have been set.
a
It won’t if
createResourceSync
hasn’t returned yet
This is the only way to solve the issue AFAIK:
Copy code
val ref: AtomicReference<Resource?> = AtomicReference()
withContext(Dispatchers.Default) {
    ref.set(createResourceSync())
    if (!isActive) {
        ref.getAndSet(null)?.delete()
    }
}

val resource = ref.getAndSet(null)
try {
    // Do stuff with resource
} finally {
    resource?.delete()
}
d
I don't think you need atomics for that, since your code is synchronous.
a
My code is not sync since I use the default dispatcher
d
withContext
is synchronous.
It may run on a different thread but it's still synchronous.
a
Well, it depends on what your definition of synchronous is. Regardless you need the atomic ref unless you’re ok with deleting the resource twice
d
By synchronous, I mean sequential. No concurrency.
a
That is correct, sort of. If cancelled while
createResourceSync
is running the exception will be thrown (and enter the finally block) while still running
createResourceSync
so in a sense it is concurrent in some circumstances
d
Hmm, I see what you're saying but I doubt they'd let concurrency be introduced. As it complicates things like it is right now.
a
Well, regardless that is how it works 🙂
d
I don't know enough to argue this but I found something you might like.
Add that to
withContext
🙂
myScope.launch { // If the job is canceled before createResourceSync() returns the resource is lost in transit val resource: ResourceType try { withContext(Dispatchers.Default + NonCancellable) { resource = createResourceSync() } } finally { resource.delete() } }
a
NonCancellable won’t solve the issue since the parent cannot be resumed after cancelling. It just means don’t interrupt whatever is running, it will still throw an exception instantly
d
But
finally
would run and
resource
will be set.
a
Finally would run but resource would not have been set yet
Actually, I believe it will be resumed when using
NonCancellable
but it will still cause issues if the code within
withContext
uses any coroutines
One problem with
NonCancellable
is that
withContext
won’t throw so you’d have to check
isActive
manually