JakeWharton/retrofit2-kotlin-coroutines-adapter

[Question] Coroutine cancellation is not handled, right?

LouisCAD opened this issue · 14 comments

The coroutineContext is not shared with the calling coroutine, right?
Did anyone find a way to wrap these calls so cancel() is called on the returned Deferred instance if the calling coroutine is cancelled?

Also, @JakeWharton, I remember you talking about an idea on how to directly support suspend functions in Retrofit in a Tweet. Would it handle cancellation and cancel any in progress request?

Oh, it seems it's already WIP in the jakew/suspend/2018-02-16 branch, using Retrofit 2.4.0 (SNAPSHOT in the WIP latest commit), and it seems it'd handle cancellation thanks to the coroutineContext property in the Continuation interface!

@LouisCAD can you give a usage example?

@LouisCAD I asked for an example usage of the CallbackAdapter in jakew/suspend/2018-02-16 branch. I don't really understand the purpose of CallbackAdapters and how it is meant to support cancellations.

@Bolein The Continuation interface of Kotlin coroutines allows chaining callbacks, minimizing runnable instances allocation, by using a state machine, but the current version of Retrofit is not compatible. The suspend prototype allows to write adapters that work on Continuation or alike concepts.

Without this, the best you can get in regards to coroutines support is what is in this library: https://github.com/gildor/kotlin-coroutines-retrofit

@LouisCAD that is still not quite what I asked.

However, I found an answer myself and feel responsible to share it with anyone interested.

This repository contains an adapter that enables the use of coroutines with Retrofit methods. The master branch contains a simple CallAdapter that converts the original Retrofit's Call<T> into a Deferred<T>, a so-called "non-blocking future". The await method must be called on a Deferred object in order for the function to be suspended.

Usage example:

@GET("weather")
fun getWeather() : Deferred<WeatherResponse>

There is another way of executing Retrofit requests by using a Callback<T>. This callback must be the last parameter in the method.

@GET("weather")
fun getWeather(callback : Callback<WeatherResponse>)

This approach to execution is limited, because it does not provide a way to cancel the request. However, this approach has something in common with the implementation of suspending functions on JVM.

@GET("weather")
suspend fun getWeather() : WeatherResponse

It turns out, that functions marked as suspend in Kotlin are compiled into JVM methods that use a sort of callbacks themselves to return the execution after completion or error. Previous Kotlin method will compile into this equivalent in Java:

@GET("weather")
@Nullable
Object getWeather(@NotNull Continuation<WeatherResponse> var1);

Continuation can be viewed as a callback that will be used by suspending function. So what @JakeWharton did by introducing CallbackAdapters is a way to use the Continuation<T> to deliver the result of the request the way we would do it with a Callback<T>.

This allows us to mark Retrofit methods as suspending functions and never explicitly await them. In addition, as @LouisCAD pointed out, such approach has an important advantage over the regular Callback<T>: it supports cancellation.

@JakeWharton please correct me if I got anything wrong.

Currently, I can't find any signs of the cancellation support. @JakeWharton what is the status of this feature? I believe that there is no way to be notified of the Job cancellation through the Continuation interface at this moment.

Don't these invokeOnCompletion calls handle cancellation?

Doesn't context get passed when you call await() method?

hrach commented

This seems like a pretty serious issue. Now when coroutines getting non-experimental it would be great to continue with the work and get CallbackAdapter to (stable) retrotif (release) to allow proper implementation for suspend and also include this lib into the retrofit itself eventually.

I'm sorry for digging this up but as for today does it mean that Retrofit and Coroutines are unusable?

Basically if I start a network call with a coroutine and cancel it when its ongoing (AAC ViewModel is being cleared) the app crashes with an uncaught exception from the CallAdapter.

Anything I can do or just wait for the first-hand suspend support release? Cheers