1
votes

I'm having issues integrating with Firestore in the Google App Engine Standard environment (using Java8, Kotlin and Ktor). The Firestore client hangs when I attempt to perform any operation. It works fine when running in the dev appserver but doesn't when running in production.

I'm running:

  • Ktor 1.1.2
  • Kotlin 1.3.21
  • com.google.cloud:google-cloud-firestore (version 0.81.0-beta)
  • Firestore in native mode

I've tested with this simple route:

get("/test") {
    try {
        val firestore = FirestoreOptions.getDefaultInstance().service
        val user = firestore.collection("user").document("testusername").get().get()
        call.respond(user.getString("name")!!)
    } catch (ex: Exception) {
        call.respond("Exception: " + ex)
    }
}

Visiting /test in a web browser causes the site to hang for a minute before a 500 error is returned.

The following exception is logged in Google Cloud Platform:

[e~website/20190306t125554.416577836925362605].<stdout>: 2019-03-06 13:01:31.306 [RequestEDA5B9B5] ERROR Application - Unhandled: GET - /test
java.lang.InterruptedException: null
    at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:506)
    at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:83)
    at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture.java:62)
    at com.example.MainApplicationKt$run$4$2.invokeSuspend(MainApplication.kt:93)
    at com.example.MainApplicationKt$run$4$2.invoke(MainApplication.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.routing.Routing.executeResult(Routing.kt:148)
    at io.ktor.routing.Routing.interceptor(Routing.kt:29)
    at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:93)
    at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:63)
    at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.features.CallLogging$Feature$install$2.invokeSuspend(CallLogging.kt:130)
    at io.ktor.features.CallLogging$Feature$install$2.invoke(CallLogging.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:106)
    at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:278)
    at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(PipelineContext.kt:63)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:137)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:157)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:23)
    at io.ktor.server.servlet.KtorServlet$blockingService$1.invokeSuspend(KtorServlet.kt:125)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:410)
    at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)
    at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
    at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:160)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:52)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at io.ktor.server.servlet.KtorServlet.blockingService(KtorServlet.kt:96)
    at io.ktor.server.servlet.KtorServlet.service(KtorServlet.kt:57)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:848)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1772)
    at com.google.apphosting.utils.servlet.JdbcMySqlConnectionCleanupFilter.doFilter(JdbcMySqlConnectionCleanupFilter.java:60)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:524)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
    at com.google.apphosting.runtime.jetty9.ParseBlobUploadHandler.handle(ParseBlobUploadHandler.java:119)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1182)
    at com.google.apphosting.runtime.jetty9.AppEngineWebAppContext.doHandle(AppEngineWebAppContext.java:183)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at com.google.apphosting.runtime.jetty9.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:296)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
    at org.eclipse.jetty.server.Server.handle(Server.java:539)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:333)
    at com.google.apphosting.runtime.jetty9.RpcConnection.handle(RpcConnection.java:202)
    at com.google.apphosting.runtime.jetty9.RpcConnector.serviceRequest(RpcConnector.java:81)
    at com.google.apphosting.runtime.jetty9.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:123)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchServletRequest(JavaRuntime.java:695)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchRequest(JavaRuntime.java:658)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:628)
    at com.google.apphosting.runtime.JavaRuntime$NullSandboxRequestRunnable.run(JavaRuntime.java:820)
    at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:269)
    at java.lang.Thread.run(Thread.java:748)

Here are the contents of my application.conf file:

ktor {
    application {
        modules = [com.example.MainApplicationKt.main]
    }
}

I've tested:

  • Enabling billing
  • Creating a new project from scratch
  • Using the com.google.firebase:firebase-admin package (version 6.7.0) instead (with the same result)
  • Using Datastore instead (which seems to work fine but switching from Firestore would require me to move the whole project and rewrite a lot of code)
1
Can you post application.conf file ..?Moinkhan
@Moinkhan, sure. I've updated the post with the contents of my application.conf file.strmstn
may be your code is throwing exception... can you wrap with try catch ... and respond with empty string in catch clause ..Moinkhan
@Moinkhan, I just tried catching all exceptions for the test code but I get the same result as before.strmstn

1 Answers

0
votes

I had a similar problem running on the App Engine Flex environment, and eventually figured out that the default instance size that Flex provides (0.6 GB) was too small to support the Firestore APIs. So, in the Flex case I needed to adjust my app.yaml file to include a "resources" section like so:

resources:
  cpu: 2
  memory_gb: 2.3
  disk_size_gb: 10

Reference on resource settings in App Engine Flexible deployments: https://cloud.google.com/appengine/docs/flexible/custom-runtimes/configuring-your-app-with-app-yaml#resource-settings

For App Engine Standard I would expect you may need to do something similar by adjusting the "instance-class" in your app-engine.xml file. "F1" is the default instance class in App Engine Standard, which has a 128 MB limit with the Java 8 runtime and a 256 MB limit with the Java 11 runtime. So, you may want to try moving to "F2" or "F4".

Reference on configuring the instance class in App Engine Standard java deployments: https://cloud.google.com/appengine/docs/standard/java/config/appref#syntax

Reference on App Engine instance classes: https://cloud.google.com/appengine/docs/standard/#instance_classes