I need to use Kotlin' coroutines and the retrofit2 library to make http requests.
When I run the following code, the http request works as expected (posts
is printed), but after this, the program hangs (blocks) and only exits after one minute.
My questions are:
import retrofit2.http.GET
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import kotlinx.coroutines.*
data class Post(
val userId: Int,
val id: Int,
val title: String
)
interface ApiService {
@GET("posts")
suspend fun getPosts(): List<Post>
}
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
val instance: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
fun main() = runBlocking {
val posts = RetrofitClient.instance.getPosts()
posts.forEach { println(it) }
}
The application doesn't exit because some non-daemon threads are still running.
Retrofit uses OkHttpClient
, which creates threads that stay alive even after requests finish.
These threads are typically kept alive for connection reuse.
You can see these threads by adding this code at the end of the main
function:
Thread.getAllStackTraces()
.keys
.filter { !it.isDaemon }
.forEach { println(it.name) }
I would suggest 2 options:
OkHttpClient
that doesn’t keep non-daemon threads runningYou need to build such OkHttpClient
and pass it when building Retrofit
.
In your original code, you don’t call client(httpClient)
, so Retrofit
uses the default OkHttpClient
.
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
val httpClient by lazy {
val dispatcher = Dispatcher(
Executors.newCachedThreadPool {
Thread(it).apply { isDaemon = true }
}
)
OkHttpClient.Builder()
.dispatcher(dispatcher)
.connectionPool(ConnectionPool(0, 1, TimeUnit.MILLISECONDS))
.build()
}
val instance: ApiService by lazy {
Retrofit.Builder()
.client(httpClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
fun main() = runBlocking {
val posts = RetrofitClient.instance.getPosts()
posts.forEach { println(it) }
}
OkHttpClient
's internal resourcesWrite a closeHttpClient()
function that closes OkHttpClient
,
and call it at the end of the main
function.
The implementation of closeHttpClient()
follows the guidance from "Shutdown isn't necessary" of OkHttpClient documentation.
object RetrofitClient {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val httpClient by lazy { OkHttpClient.Builder().build() }
val instance: ApiService by lazy {
Retrofit.Builder()
.client(httpClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
fun closeHttpClient() {
httpClient.dispatcher().executorService().shutdown()
httpClient.connectionPool().evictAll()
httpClient.cache()?.close()
}
}
fun main() = runBlocking {
val posts = RetrofitClient.instance.getPosts()
posts.forEach { println(it) }
RetrofitClient.closeHttpClient()
}