Skip to content

Bundle the Cloud Tasks client in the runtime jars and add an e2e session test - #547

Merged
copybara-service[bot] merged 1 commit into
mainfrom
cloudtasks-runtime-jar-session-test
Oct 24, 2026
Merged

copybara-service[bot] merged 1 commit into
mainfrom
cloudtasks-runtime-jar-session-test

Conversation

@ludoch

@ludoch ludoch commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Problem

The runtime jars (runtime_impl_jetty12, runtime_impl_jetty121) include every class in com/google/appengine/api/taskqueue/* from appengine-apis. Since fd92dbe that package contains CloudTasksClientWrapper and TaskProcessor (and the jars now include SweeperServlet), but none of the Cloud Tasks client libraries they depend on, and none of them are relocated.

The runtime uses the Task Queue API itself, from its private class loader: with <async-session-persistence>, DeferredDatastoreSessionStore adds a deferred task on every session save. With APPENGINE_USE_CLOUDTASK_PUSH_QUEUE=true, that goes through the runtime's copy of QueueImpl into CloudTasksClientWrapper, and the request fails:

500 java.lang.NoClassDefFoundError: com/google/auth/Credentials
  at com.google.appengine.api.taskqueue.QueueImpl.isCloudTaskBackendEnabled(QueueImpl.java:1004)
  at com.google.appengine.api.taskqueue.QueueImpl.add(QueueImpl.java:458)
  at com.google.apphosting.runtime.jetty.DeferredDatastoreSessionStore.doStore(DeferredDatastoreSessionStore.java:81)

appengine-api-1.0-sdk is not affected: it bundles and relocates the whole client stack.

Fix

In both runtime poms:

  • Bundle the Cloud Tasks client and its dependencies (the same set as appengine-api-1.0-sdk, minus Cloud Storage): google-cloud-tasks, the v2/v2beta3 protos and gRPC stubs, proto-google-iam-v1, gax, gax-grpc, gax-httpjson (referenced by the CloudTasksClient constructor), api-common, protobuf-java-util, grpc-*, google-auth-library-*, google-http-client/-gson, opencensus, opentelemetry, threetenbp.
  • Add ServicesResourceTransformer, so gRPC's META-INF/services provider files (name resolvers, load balancers, channel providers) are merged instead of overwriting each other.
  • Exclude grpc-netty-shaded's META-INF/native/**, as the SDK jar does.

Test

New CloudTasksSessionTest (runtime e2e, parameterized like JavaRuntimeAllInOneTest): runs the allinone app with <async-session-persistence enabled="true"/> and APPENGINE_USE_CLOUDTASK_PUSH_QUEUE=true, makes the session requests, and checks that they're served and that the runtime output has no ClassNotFoundException/NoClassDefFoundError. JavaRuntimeViaHttpBase.RuntimeContext gets an outputLinesContaining(Pattern) helper for that.

There is no Cloud Tasks endpoint override, so the client really calls cloudtasks.googleapis.com with fake project 1 and gets an auth/permission error. The test therefore doesn't check that the session content survives the deferred write, only that the whole client stack loads and runs from the runtime class loader.

Results (JDK 25, Jetty 12.1 EE8 and EE11):

  • Without the fix: both fail with the NoClassDefFoundError: com/google/auth/Credentials above.
  • With the fix: both pass with no class loading errors; the client reaches the service and gets PERMISSION_DENIED: Permission denied on resource project 1.

The java17/java21 combinations (Jetty 12.0 jar) were not run locally; the Jetty 12.0 and 12.1 jars contain the same Cloud Tasks classes and merged gRPC service files. CI covers those JDKs.

…ion test

The runtime jars (runtime_impl_jetty12/121) include every class of
com.google.appengine.api.taskqueue, so since fd92dbe they also contain
CloudTasksClientWrapper and TaskProcessor, but none of the Cloud Tasks client
libraries they use. The runtime itself uses the Task Queue API from its private
class loader, e.g. DeferredDatastoreSessionStore for async session
persistence. With APPENGINE_USE_CLOUDTASK_PUSH_QUEUE=true, storing a session
then fails the request with
NoClassDefFoundError: com/google/auth/Credentials.

Add google-cloud-tasks and its dependencies (gax, gax-grpc, gax-httpjson,
grpc, google-auth-library, google-http-client, opencensus, opentelemetry,
threetenbp) to both runtime jars, merge META-INF/services files so gRPC finds
its providers, and exclude the netty native libraries as the SDK jar does.

CloudTasksSessionTest runs the allinone app with async session persistence and
the Cloud Tasks flag enabled, and checks that the session request is served
and that the runtime logs no class loading errors.
@ludoch

ludoch commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Runtime jar size impact

Measured locally (JDK 25): runtime_impl_jetty12 / runtime_impl_jetty121 built from origin/main (5777903) vs. this PR (88187cd), mvn package -DskipTests.

Jar main This PR Δ Entries Classes
runtime-impl-jetty12.jar 23.1 MB 33.5 MB +10.4 MB (+45%) 12,027 → 18,802 10,864 → 17,335
runtime-impl-jetty121.jar 23.3 MB 33.7 MB +10.4 MB (+44%) 12,146 → 18,921 10,974 → 17,445

The runtime deployment ships both jars, so it grows by about 20.8 MB in total. No existing entries were removed. appengine-api-1.0-sdk.jar is not changed by this PR.

Where the added bytes come from (runtime-impl-jetty121.jar, compressed entry data; the remaining ~1.5 MB is zip headers for the ~6,800 new entries):

Package Compressed Entries
io/grpc/netty/shaded (grpc-netty-shaded, natives excluded) 4.04 MB 2,956
com/google/cloud/tasks (google-cloud-tasks, v2/v2beta3 protos + stubs) 1.19 MB 516
io/grpc (grpc-api, core, stub, protobuf, util, auth, context) 1.04 MB 990
com/google/api/gax (gax, gax-grpc, gax-httpjson) 0.75 MB 694
org/threeten/bp (threetenbp) 0.47 MB 216
com/google/auth (google-auth-library) 0.31 MB 205
io/opencensus 0.31 MB 413
com/google/api/client (google-http-client) 0.26 MB 251
io/opentelemetry 0.18 MB 231
com/google/iam (proto-google-iam-v1) 0.15 MB 70
com/google/protobuf/util, com/google/api core, META-INF, other 0.22 MB 233
Total 8.92 MB 6,775

grpc-netty-shaded alone accounts for about 45% of the increase. Possible follow-ups to shrink it, not done here:

  • Use a lighter gRPC transport (e.g. grpc-okhttp) for the runtime copy of the Cloud Tasks client.
  • Exclude the v2beta3 client from the runtime jars if the runtime never needs QueueStatistics. It is used only by fetchStatisticsAsync, which the runtime's own session/deferred-task code doesn't call.

@copybara-service
copybara-service Bot merged commit 74282d7 into main Oct 24, 2026
14 checks passed
@copybara-service
copybara-service Bot deleted the cloudtasks-runtime-jar-session-test branch October 24, 2026 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants