google-maps-services-java
google-maps-services-java copied to clipboard
Using Ktor results in CallNotFoundException due to work on wrong thread
I'm using Ktor, which is based on coroutines. This seems to be conflicting with the thread manager that the Google Maps Platform Java library uses. It keeps giving me an Unexpected exception from com.google.apphosting.api.ApiProxy$CallNotFoundException: Can't make API call urlfetch.Fetch in a thread that is neither the original request thread nor a thread created by ThreadManager
error.
Environment details
- Specify the API at the beginning of the title (for example, "Places: ...")
- OS type and version
- Library version and other environment information
Places Autocomplete Kotlin 1.4 0.11.0
Steps to reproduce
- Make a request to
PlacesApi.placeAutocomplete(context, query, null).await()
Code example
fun Application.placesAutocomplete() {
routing {
get("/v1/places/autocomplete") {
val parameters = call.request.queryParameters
val query: String? = parameters["query"]
val type: String? = parameters["type"]
if (query == null) {
call.respondParameterRequired("query")
return@get
}
if (query.isBlank()) {
call.respond(AutocompleteResponse(emptyList()))
return@get
}
val PLACES_KEY = System.getenv("PLACES_KEY")
val placeAutocompleteTypes = try {
type?.let { PlaceAutocompleteType.valueOf(type) }
} catch (e: IllegalArgumentException) {
call.respond(HttpStatusCode.BadRequest, "Invalid place type")
return@get
}
val context = GeoApiContext.Builder(GaeRequestHandler.Builder()).apiKey(PLACES_KEY).build()
val placeAutocompleteRequest = PlacesApi.placeAutocomplete(context, query, null)
placeAutocompleteTypes?.let { types ->
placeAutocompleteRequest.types(types)
}
try {
val placeAutocompleteResponse = withContext(Dispatchers.IO) { placeAutocompleteRequest.await() }
val places = placeAutocompleteResponse.map {
val formatting = it.structuredFormatting
Place(
placeId = it.placeId, mainText = formatting.mainText
?: "", secondaryText = formatting.secondaryText
?: ""
)
}
val autocompleteResponse = AutocompleteResponse(places)
call.respond(autocompleteResponse)
} catch (e: Exception) {
call.respond(HttpStatusCode.InternalServerError, e)
}
}
}
}
Stack trace
com.google.maps.errors.UnknownErrorException: Unexpected exception from com.google.apphosting.api.ApiProxy$CallNotFoundException: Can't make API call urlfetch.Fetch in a thread that is neither the original request thread nor a thread created by ThreadManager
at com.google.maps.internal.GaePendingResult.await(GaePendingResult.java:121)
at com.google.maps.PendingResultBase.await(PendingResultBase.java:58)
at appengine.PlacesAutocompleteKt$placesAutocomplete$1$1$placeAutocompleteResponse$1.invokeSuspend(PlacesAutocomplete.kt:74)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:272)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at appengine.PlacesAutocompleteKt$placesAutocomplete$1$1.invokeSuspend(PlacesAutocomplete.kt:74)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:175)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:137)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:108)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:307)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:317)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:249)
at retrofit2.KotlinExtensions$awaitResponse$2$2.onResponse(KotlinExtensions.kt:93)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:174)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Hi @mochat97, based on the stacktrace you shared, you will need to create the full request object (specifically the GeoApiContext
which creates a OkHttpRequestHandler
internally) inside the coroutine scope where the request is invoked.
So:
try {
val placeAutocompleteResponse = withContext(Dispatchers.IO) {
val context = GeoApiContext.Builder(GaeRequestHandler.Builder()).apiKey(PLACES_KEY).build()
val placeAutocompleteRequest = PlacesApi.placeAutocomplete(context, query, null)
placeAutocompleteTypes?.let { types ->
placeAutocompleteRequest.types(types)
}
placeAutocompleteRequest.await()
}
Let me know if this resolves your issue.
Still doesn't work :(
I'm not very familiar with Ktor but seems like the only solution here would be to create a custom Dispatcher
(instead of Dispatchers.IO
) that pulls new threads from ThreadManager.
As a HTTP GET request is synchronous by nature, do you need to spawn a new coroutine at the point you are in the code causing the issue? I believe the solution would be to implement asynchronous behaviour in your client.