Skip to content

Commit 46c1625

Browse files
authored
Add virtual thread support for gRPC and HTTP servers on JDK 25+ (#13705)
Add virtual thread support for gRPC and Armeria HTTP servers on JDK 25+ Enable virtual-thread-per-task executors for all 4 gRPC and 7 HTTP server handler pools when running on JDK 25+, with automatic fallback to platform thread pools on older JDKs. JDK 25 is required as the first LTS with the synchronized pinning fix (JEP 491). Key changes: - VirtualThreads: reflection-based detection and executor factory - VirtualThreadScheduledExecutor: virtual-thread-backed scheduled executor for Armeria's blockingTaskExecutor - Default Docker image changed to JDK 25; JDK 11/17/21 variants kept - Kill switch: SW_VIRTUAL_THREADS_ENABLED=false On JDK 25+, ~9 ForkJoinPool carrier threads replace up to 800+ platform threads across all handler pools.
1 parent b39805b commit 46c1625

File tree

18 files changed

+700
-11
lines changed

18 files changed

+700
-11
lines changed

.github/workflows/publish-docker.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ jobs:
7575
uses: docker/setup-qemu-action@v3
7676
- name: Set up Docker Buildx
7777
uses: docker/setup-buildx-action@v3
78+
- name: Build and push docker images based on Java 11
79+
env:
80+
SW_OAP_BASE_IMAGE: eclipse-temurin:11-jre
81+
TAG: ${{ env.TAG }}-java11
82+
run: make build.all docker.push
7883
- name: Build and push docker images based on Java 17
7984
env:
8085
SW_OAP_BASE_IMAGE: eclipse-temurin:17-jre
@@ -85,11 +90,6 @@ jobs:
8590
SW_OAP_BASE_IMAGE: eclipse-temurin:21-jre
8691
TAG: ${{ env.TAG }}-java21
8792
run: make build.all docker.push
88-
- name: Build and push docker images based on Java 25
89-
env:
90-
SW_OAP_BASE_IMAGE: eclipse-temurin:25-jre
91-
TAG: ${{ env.TAG }}-java25
92-
run: make build.all docker.push
9393
- name: Build and push docker images
9494
run: make build.all docker.push
9595
- name: Build and push data-generator image

docker/oap/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
ARG BASE_IMAGE='eclipse-temurin:11-jre'
17+
ARG BASE_IMAGE='eclipse-temurin:25-jre'
1818

1919
FROM $BASE_IMAGE
2020

docs/en/changes/changes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
* Add `CLAUDE.md` as AI assistant guide for the project.
1313
* Upgrade Groovy to 5.0.3 in OAP backend.
1414
* Bump up nodejs to v24.13.0 for the latest UI(booster-ui) compiling.
15+
* Add virtual thread support (JDK 25+) for gRPC and Armeria HTTP server handler threads.
16+
Set `SW_VIRTUAL_THREADS_ENABLED=false` to disable.
17+
18+
| Pool | Threads (JDK < 25) | Threads (JDK 25+) |
19+
|---|---|---|
20+
| gRPC server handler (`core-grpc`, `receiver-grpc`, `als-grpc`, `ebpf-grpc`) | Cached platform (unbounded) | Virtual threads |
21+
| HTTP blocking (`core-http`, `receiver-http`, `promql-http`, `logql-http`, `zipkin-query-http`, `zipkin-http`, `firehose-http`) | Cached platform (max 200) | Virtual threads |
22+
| VT carrier threads (ForkJoinPool) | N/A | ~9 shared |
23+
24+
On JDK 25+, all 11 thread pools above share ~9 carrier threads instead of up to 1,400+ platform threads.
25+
* Change default Docker base image to JDK 25 (`eclipse-temurin:25-jre`). JDK 11 kept as `-java11` variant.
1526

1627
#### OAP Server
1728

docs/en/setup/backend/configuration-vocabulary.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,16 @@ OAP will query the data from the "hot and warm" stage by default if the "warm" s
542542
| property | - | - | The group settings of property, such as UI and profiling. | - | - |
543543
| - | shardNum | - | Shards Number for property group. | SW_STORAGE_BANYANDB_PROPERTY_SHARD_NUM | 1 |
544544
| - | replicas | - | Replicas for property group. |SW_STORAGE_BANYANDB_PROPERTY_REPLICAS | 0 |
545+
546+
## Standalone Environment Variables
547+
The following environment variables are **not** backed by `application.yml`. They are read directly from the
548+
process environment and take effect across all modules.
549+
550+
| Environment Variable | Value(s) and Explanation | Default |
551+
|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
552+
| SW_OAL_ENGINE_DEBUG | Set to any non-empty value to dump OAL-generated `.class` files to disk (under the `oal-rt/` directory relative to the OAP working path). Useful for debugging code generation issues. Leave unset in production. | (not set, no files written) |
553+
| SW_VIRTUAL_THREADS_ENABLED | Set to `false` to disable virtual threads on JDK 25+. On JDK 25+, gRPC server handler threads and HTTP blocking task executors are virtual threads by default. Set this variable to `false` to force traditional platform thread pools. Ignored on JDK versions below 25. | (not set, virtual threads enabled on JDK 25+) |
554+
545555
## Note
546556

547557
¹ System Environment Variable name could be declared and changed in `application.yml/bydb.yaml`. The names listed here are simply

oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
249249
if (moduleConfig.getGRPCThreadPoolSize() > 0) {
250250
grpcServer.setThreadPoolSize(moduleConfig.getGRPCThreadPoolSize());
251251
}
252+
grpcServer.setThreadPoolName("core-grpc");
252253
grpcServer.initialize();
253254

254255
HTTPServerConfig httpServerConfig = HTTPServerConfig.builder()
@@ -264,6 +265,7 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
264265
setBootingParameter("oap.external.http.host", moduleConfig.getRestHost());
265266
setBootingParameter("oap.external.http.port", moduleConfig.getRestPort());
266267
httpServer = new HTTPServer(httpServerConfig);
268+
httpServer.setBlockingTaskName("core-http");
267269
httpServer.initialize();
268270

269271
this.registerServiceImplementation(ConfigService.class, new ConfigService(moduleConfig, this));

oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/grpc/GRPCServer.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.skywalking.oap.server.library.server.ServerException;
3939
import org.apache.skywalking.oap.server.library.server.grpc.ssl.DynamicSslContext;
4040
import org.apache.skywalking.oap.server.library.server.pool.CustomThreadFactory;
41+
import org.apache.skywalking.oap.server.library.util.VirtualThreads;
4142

4243
@Slf4j
4344
public class GRPCServer implements Server {
@@ -53,6 +54,7 @@ public class GRPCServer implements Server {
5354
private String trustedCAsFile;
5455
private DynamicSslContext sslContext;
5556
private int threadPoolSize;
57+
private String threadPoolName = "grpcServerPool";
5658
private static final Marker SERVER_START_MARKER = MarkerFactory.getMarker("Console");
5759

5860
public GRPCServer(String host, int port) {
@@ -72,6 +74,10 @@ public void setThreadPoolSize(int threadPoolSize) {
7274
this.threadPoolSize = threadPoolSize;
7375
}
7476

77+
public void setThreadPoolName(String threadPoolName) {
78+
this.threadPoolName = threadPoolName;
79+
}
80+
7581
/**
7682
* Require for `server.crt` and `server.pem` for open ssl at server side.
7783
*
@@ -96,11 +102,21 @@ public void initialize() {
96102
if (maxMessageSize > 0) {
97103
nettyServerBuilder.maxInboundMessageSize(maxMessageSize);
98104
}
99-
if (threadPoolSize > 0) {
100-
ExecutorService executor = new ThreadPoolExecutor(
101-
threadPoolSize, threadPoolSize, 60, TimeUnit.SECONDS, new SynchronousQueue<>(),
102-
new CustomThreadFactory("grpcServerPool"), new CustomRejectedExecutionHandler()
103-
);
105+
final ExecutorService executor = VirtualThreads.createExecutor(
106+
threadPoolName,
107+
() -> {
108+
if (threadPoolSize > 0) {
109+
return new ThreadPoolExecutor(
110+
threadPoolSize, threadPoolSize, 60, TimeUnit.SECONDS,
111+
new SynchronousQueue<>(),
112+
new CustomThreadFactory(threadPoolName),
113+
new CustomRejectedExecutionHandler()
114+
);
115+
}
116+
return null;
117+
}
118+
);
119+
if (executor != null) {
104120
nettyServerBuilder.executor(executor);
105121
}
106122

oap-server/server-library/library-server/src/main/java/org/apache/skywalking/oap/server/library/server/http/HTTPServer.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
import java.time.Duration;
3636
import java.util.List;
3737
import java.util.Set;
38+
import java.util.concurrent.ScheduledExecutorService;
3839

3940
import lombok.extern.slf4j.Slf4j;
4041
import org.apache.skywalking.oap.server.library.server.Server;
4142
import org.apache.skywalking.oap.server.library.server.ssl.PrivateKeyUtil;
43+
import org.apache.skywalking.oap.server.library.util.VirtualThreads;
4244

4345
import static java.util.Objects.requireNonNull;
4446

@@ -48,11 +50,16 @@ public class HTTPServer implements Server {
4850
protected ServerBuilder sb;
4951
// Health check service, supports HEAD, GET method.
5052
protected final Set<HttpMethod> allowedMethods = Sets.newHashSet(HttpMethod.HEAD);
53+
private String blockingTaskName = "http-blocking";
5154

5255
public HTTPServer(HTTPServerConfig config) {
5356
this.config = config;
5457
}
5558

59+
public void setBlockingTaskName(final String blockingTaskName) {
60+
this.blockingTaskName = blockingTaskName;
61+
}
62+
5663
@Override
5764
public void initialize() {
5865
sb = com.linecorp.armeria.server.Server
@@ -93,6 +100,14 @@ public void initialize() {
93100
sb.absoluteUriTransformer(this::transformAbsoluteURI);
94101
}
95102

103+
if (VirtualThreads.isSupported()) {
104+
final ScheduledExecutorService blockingExecutor = VirtualThreads.createScheduledExecutor(
105+
blockingTaskName, () -> null);
106+
if (blockingExecutor != null) {
107+
sb.blockingTaskExecutor(blockingExecutor, true);
108+
}
109+
}
110+
96111
log.info("Server root context path: {}", config.getContextPath());
97112
}
98113

0 commit comments

Comments
 (0)