From 87cbe073cf7d96bb18c7588d428fc4dff6803c9c Mon Sep 17 00:00:00 2001 From: Chase Engdall <173094250+cengdall-blaze@users.noreply.github.com> Date: Tue, 29 Apr 2025 12:48:58 -0700 Subject: [PATCH] Add abstract coroutine class and have `ImplBase` extend it Spring application is failing to start when bindService is final: ``` WARN: Unable to proxy interface-implementing method [public final io.grpc.ServerServiceDefinition com.backblaze.ServiceImplBase.bindService()] because it is marked as final, consider using interface-based JDK proxies instead. ``` CGLIB proxies are required to apply Spring AOP to grpc service implementations if there isn't an interface. Wire's output (as well as that of gprc-java) generates an abstract class, so Spring is attempting to generate CGLIB proxies and failing due to not being able to override the bindService method. This change hsa `ImplBase` extend an abstract class when generating suspending calls, which itself implements `WireBindableService`. This is a similar pattern to what is being done by grpc-java. --- .../kotlin/grpcserver/ImplBaseGenerator.kt | 12 ++++------ .../src/test/golden/unitService.kt | 6 ++--- server/api/server.api | 7 ++++++ .../grpcserver/AbstractCoroutineServerImpl.kt | 24 +++++++++++++++++++ 4 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 server/src/main/java/com/squareup/wire/kotlin/grpcserver/AbstractCoroutineServerImpl.kt diff --git a/server-generator/src/main/java/com/squareup/wire/kotlin/grpcserver/ImplBaseGenerator.kt b/server-generator/src/main/java/com/squareup/wire/kotlin/grpcserver/ImplBaseGenerator.kt index 403ca46..cbbfee9 100644 --- a/server-generator/src/main/java/com/squareup/wire/kotlin/grpcserver/ImplBaseGenerator.kt +++ b/server-generator/src/main/java/com/squareup/wire/kotlin/grpcserver/ImplBaseGenerator.kt @@ -22,7 +22,6 @@ import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.UNIT import com.squareup.kotlinpoet.asClassName @@ -44,13 +43,13 @@ object ImplBaseGenerator { .addType( TypeSpec.classBuilder("${service.name}ImplBase") .addModifiers(KModifier.ABSTRACT) - .addSuperinterface(WireBindableService::class) - .apply { addImplBaseConstructor(options) } + .apply { if (options.suspendingCalls) superclass(AbstractCoroutineServerImpl::class) else addSuperinterface(WireBindableService::class) } + .apply { addImplBaseConstructor(this, options) } .apply { addImplBaseBody(generator, this, service, options) } .build(), ) - private fun TypeSpec.Builder.addImplBaseConstructor(options: KotlinGrpcGenerator.Companion.Options) { + private fun TypeSpec.Builder.addImplBaseConstructor(builder: TypeSpec.Builder, options: KotlinGrpcGenerator.Companion.Options) { if (options.suspendingCalls) { this.primaryConstructor( FunSpec.constructorBuilder() @@ -63,11 +62,8 @@ object ImplBaseGenerator { ).build(), ) .build(), - ).addProperty( - PropertySpec.builder("context", CoroutineContext::class, KModifier.PROTECTED) - .initializer("context") - .build(), ) + builder.addSuperclassConstructorParameter("context") } } diff --git a/server-generator/src/test/golden/unitService.kt b/server-generator/src/test/golden/unitService.kt index 52fddbe..81386b7 100644 --- a/server-generator/src/test/golden/unitService.kt +++ b/server-generator/src/test/golden/unitService.kt @@ -1,7 +1,7 @@ // Code generated by Wire protocol buffer compiler, do not edit. import com.google.protobuf.DescriptorProtos import com.google.protobuf.Descriptors -import com.squareup.wire.kotlin.grpcserver.WireBindableService +import com.squareup.wire.kotlin.grpcserver.AbstractCoroutineServerImpl import com.squareup.wire.kotlin.grpcserver.WireMethodMarshaller import io.grpc.CallOptions import io.grpc.Channel @@ -110,9 +110,9 @@ public object MyServiceWireGrpc { public fun newStub(channel: Channel): MyServiceStub = MyServiceStub(channel) public abstract class MyServiceImplBase( - protected val context: CoroutineContext = kotlin.coroutines.EmptyCoroutineContext + context: CoroutineContext = kotlin.coroutines.EmptyCoroutineContext , - ) : WireBindableService { + ) : AbstractCoroutineServerImpl(context) { public open suspend fun doSomething(request: Unit): Unit = throw UnsupportedOperationException() final override fun bindService(): ServerServiceDefinition = diff --git a/server/api/server.api b/server/api/server.api index 22bb3b6..b84149e 100644 --- a/server/api/server.api +++ b/server/api/server.api @@ -1,3 +1,10 @@ +public abstract class com/squareup/wire/kotlin/grpcserver/AbstractCoroutineServerImpl : com/squareup/wire/kotlin/grpcserver/WireBindableService { + public fun ()V + public fun (Lkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getContext ()Lkotlin/coroutines/CoroutineContext; +} + public final class com/squareup/wire/kotlin/grpcserver/FlowAdapter { public static final field INSTANCE Lcom/squareup/wire/kotlin/grpcserver/FlowAdapter; public final fun bidiStream (Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow; diff --git a/server/src/main/java/com/squareup/wire/kotlin/grpcserver/AbstractCoroutineServerImpl.kt b/server/src/main/java/com/squareup/wire/kotlin/grpcserver/AbstractCoroutineServerImpl.kt new file mode 100644 index 0000000..d5193c6 --- /dev/null +++ b/server/src/main/java/com/squareup/wire/kotlin/grpcserver/AbstractCoroutineServerImpl.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2025 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.wire.kotlin.grpcserver + +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +abstract class AbstractCoroutineServerImpl( + /** The context in which to run server coroutines. */ + open val context: CoroutineContext = EmptyCoroutineContext, +) : WireBindableService