[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Ktor results in CallNotFoundException due to work on wrong thread #672

Open
michaeltheshah opened this issue Apr 1, 2020 · 4 comments
Labels
triage me I really want to be triaged. type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.

Comments

@michaeltheshah
Copy link

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

  1. Specify the API at the beginning of the title (for example, "Places: ...")
  2. OS type and version
  3. Library version and other environment information

Places Autocomplete
Kotlin 1.4
0.11.0

Steps to reproduce

  1. 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)
@michaeltheshah michaeltheshah added triage me I really want to be triaged. type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns. labels Apr 1, 2020
@jpoehnelt jpoehnelt assigned arriolac and unassigned jpoehnelt Apr 9, 2020
@arriolac
Copy link
Member
arriolac commented Apr 9, 2020

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.

@michaeltheshah
Copy link
Author

Still doesn't work :(

@arriolac
Copy link
Member

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.

@LDuncAndroid
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage me I really want to be triaged. type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.
Projects
None yet
Development

No branches or pull requests

4 participants