From 33b7a17574d5f4f48eca6d5ac8a21ea2e0654bb7 Mon Sep 17 00:00:00 2001 From: RishabhB99 Date: Mon, 10 Aug 2026 16:31:28 +0530 Subject: [PATCH] Add optional executor to GrpcPlatformServerDefinition The gRPC network server was always built without calling ServerBuilder.executor(...), so gRPC falls back to its default unbounded cached thread pool to run RPC handlers. Services with blocking handlers (e.g. those making downstream I/O calls) therefore pin one platform thread per concurrent in-flight RPC, with no ceiling, which can exhaust native threads under load. Expose an optional Executor on GrpcPlatformServerDefinition and wire it into the NettyServerBuilder when present. When null (the default), behavior is unchanged, so this is fully backward compatible. Services on JDK 21 can now supply Executors.newVirtualThreadPerTaskExecutor() to decouple handler dispatch from platform-thread count; others can supply a bounded pool for back-pressure. Co-Authored-By: Claude Opus 4.8 --- .../grpc/GrpcPlatformServerDefinition.java | 10 ++++++++++ .../grpc/GrpcPlatformServiceContainer.java | 3 +++ 2 files changed, 13 insertions(+) diff --git a/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServerDefinition.java b/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServerDefinition.java index 37e8946..639b045 100644 --- a/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServerDefinition.java +++ b/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServerDefinition.java @@ -4,6 +4,8 @@ import java.time.Duration; import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; +import javax.annotation.Nullable; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -22,4 +24,12 @@ public class GrpcPlatformServerDefinition { @Builder.Default Duration maxConnectionAgeGrace = Duration.ZERO; @Singular Collection serviceFactories; @Singular List serverInterceptors; + + /** + * Optional executor used by the gRPC server to run RPC handler callbacks. When {@code null} (the + * default), gRPC uses its built-in shared executor (an unbounded cached thread pool). Services + * that block inside their handlers can supply a bounded or virtual-thread executor here to + * control how handler work is dispatched. + */ + @Nullable Executor executor; } diff --git a/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServiceContainer.java b/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServiceContainer.java index 435ea0f..ea18d53 100644 --- a/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServiceContainer.java +++ b/platform-grpc-service-framework/src/main/java/org/hypertrace/core/serviceframework/grpc/GrpcPlatformServiceContainer.java @@ -280,6 +280,9 @@ protected abstract GrpcServiceContainerEnvironment buildContainerEnvironment( private ServerBuilder initializeBuilder(GrpcPlatformServerDefinition serverDefinition) { NettyServerBuilder builder = NettyServerBuilder.forPort(serverDefinition.getPort()); + if (serverDefinition.getExecutor() != null) { + builder.executor(serverDefinition.getExecutor()); + } if (serverDefinition.getMaxInboundMessageSize() > 0) { builder.maxInboundMessageSize(serverDefinition.getMaxInboundMessageSize()); }