Using coroutine cancellation for custom cleanup / shutdown?

Is it considered bad practice to catch CancellationException, do custom shutdown/cleanup code, and then rethrow the exception?

If I have a suspend function that performs some operation, and want a way to cancel that operation, I may need to perform some steps to rollback the partial changes performed by that operation. Is it OK to do that in a CancellationException? Or should cancellation procedures be left “untouched” as much as possible?

In the latter case, I can only imagine adding a separate “cancelOperation” function in the same API. Note that I was talking about an operation done by a suspend function, so the operation is done once that function no longer suspends. This means that I can’t just return some custom class that contains a “cancelOperation” function or a “dispose” function (COIL does the latter).

Yes, exception handlers is exactly the way how clean up should be performed when using coroutines. Although, it probably makes more sense to use finally instead of catch. You don’t need to rethrow and you perform clean up for any exception, not only CancellationException.

https://kotlinlang.org/docs/cancellation-and-timeouts.html#closing-resources-with-finally

Alright. Makes sense, thanks! I think there are cases where you do want to actually catch the exception though - if for example your function is comitting some changes to a persistent state, and mid-commit, a cancellation happens, then the partial commit needs to be rolled back - something you don’t normally do.

Ahh, yes, you are right. Often clean up is different for success and failure paths. Still, we don’t need to target CancellationException specifically. Standard handling by catching Throwable will work just fine.

Generally, use a finally block for cleanup, to make an obvious guarantee that it happens no matter what.

For things like commit/rollback, commit at the end of the successful path and then set a variable like committed=true. Then in the finally block, roll back if !committed.

You usually only need to actually catch an exception if you want to log it, recover, rethrow it as something else, or something like that.