https://kotlinlang.org logo
#coroutines
Title
# coroutines
a

ansman

05/01/2019, 4:49 PM
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

Dominaezzz

05/01/2019, 6:31 PM
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

ansman

05/01/2019, 6:34 PM
That would lead to the same issue
d

Dominaezzz

05/01/2019, 6:34 PM
How so?
delete
will be called.
a

ansman

05/01/2019, 6:35 PM
If the job is cancelled the resource is lost
Because the coroutine wouldn’t be resumed
d

Dominaezzz

05/01/2019, 6:36 PM
Hmm but it would be cancelled.
a

ansman

05/01/2019, 6:36 PM
Yes, but if but a cancelled coroutine doesn’t resume
d

Dominaezzz

05/01/2019, 6:37 PM
To my knowledge, cancel means resume with
CancellationException
or something.
a

ansman

05/01/2019, 6:37 PM
It does, but instantly. It doesn’t wait for the child to complete
d

Dominaezzz

05/01/2019, 6:38 PM
Oh, are you worried about what happens within
createResourceSync
?
a

ansman

05/01/2019, 6:39 PM
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

Dominaezzz

05/01/2019, 6:39 PM
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

ansman

05/01/2019, 6:40 PM
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

Dominaezzz

05/01/2019, 6:42 PM
It will be thrown by
withContext
but not within it. resource would have been set.
a

ansman

05/01/2019, 6:43 PM
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

Dominaezzz

05/01/2019, 6:46 PM
I don't think you need atomics for that, since your code is synchronous.
a

ansman

05/01/2019, 6:46 PM
My code is not sync since I use the default dispatcher
d

Dominaezzz

05/01/2019, 6:46 PM
withContext
is synchronous.
It may run on a different thread but it's still synchronous.
a

ansman

05/01/2019, 6:48 PM
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

Dominaezzz

05/01/2019, 6:48 PM
By synchronous, I mean sequential. No concurrency.
a

ansman

05/01/2019, 6:53 PM
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

Dominaezzz

05/01/2019, 6:58 PM
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

ansman

05/01/2019, 6:59 PM
Well, regardless that is how it works 🙂
d

Dominaezzz

05/01/2019, 6:59 PM
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

ansman

05/01/2019, 7:00 PM
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

Dominaezzz

05/01/2019, 7:01 PM
But
finally
would run and
resource
will be set.
a

ansman

05/01/2019, 7:02 PM
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
2 Views