This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git
The following commit(s) were added to refs/heads/main by this push:
new 68f6b4c Fix alarm webhook in native image: lazy HttpClient
initialization
68f6b4c is described below
commit 68f6b4c8c79cef8631e0fea63c96bfdf92248102
Author: Wu Sheng <[email protected]>
AuthorDate: Thu Mar 12 22:29:47 2026 +0800
Fix alarm webhook in native image: lazy HttpClient initialization
HttpAlarmCallback used a static final HttpClient field that was initialized
at native-image build time. The resulting HttpClient had dead thread pools
and IO resources, silently failing all webhook POST requests at runtime.
Replace with same-FQCN class using double-checked locking lazy init, so
the HttpClient is created on first use at runtime.
---
.../resources/replacement-source-sha256.properties | 2 +
oap-libs-for-graalvm/CLAUDE.md | 3 +-
.../server-core-for-graalvm/pom.xml | 1 +
.../oap/server/core/alarm/HttpAlarmCallback.java | 105 +++++++++++++++++++++
4 files changed, 110 insertions(+), 1 deletion(-)
diff --git
a/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
b/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
index 3d95009..bc0217f 100644
--- a/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
+++ b/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
@@ -36,6 +36,8 @@
skywalking/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server
# Java-backed closures instead of GroovyShell
skywalking/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java
= 66df4f116946217750f99a595cec4fed32aabe944dd63541f9d51970d34d7389
skywalking/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.java
= a5b899e9a47c1ddff8551264a3ff22b083c35a57698701632a590489bf488739
+# Lazy HttpClient init (replaces static-final field that breaks in native
image)
+skywalking/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/HttpAlarmCallback.java
= b9ee8524f347642712da57dd332c08ba34cef3451608d3303cb883243ce4c80f
# --- meter-analyzer-for-graalvm ---
# MAL DSL v2: loads pre-compiled MalExpression from per-file manifests
diff --git a/oap-libs-for-graalvm/CLAUDE.md b/oap-libs-for-graalvm/CLAUDE.md
index efde662..fdc5cce 100644
--- a/oap-libs-for-graalvm/CLAUDE.md
+++ b/oap-libs-for-graalvm/CLAUDE.md
@@ -41,7 +41,7 @@ JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal
make build-distro
## Complete Replacement Inventory
-### server-core-for-graalvm (7 classes)
+### server-core-for-graalvm (8 classes)
| Replacement Class | Upstream Source | Change | Staleness Tracked |
|---|---|---|---|
@@ -52,6 +52,7 @@ JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal
make build-distro
| `CoreModuleConfig` | `server-core/.../CoreModuleConfig.java` | Added
`@Setter` at class level | No |
| `HierarchyDefinitionService` |
`server-core/.../config/HierarchyDefinitionService.java` | Java-backed closures
instead of GroovyShell | No |
| `HierarchyService` | `server-core/.../hierarchy/HierarchyService.java` |
Support for Java-backed closures | No |
+| `HttpAlarmCallback` | `server-core/.../alarm/HttpAlarmCallback.java` | Lazy
HttpClient init (static final breaks in native image) | No |
### meter-analyzer-for-graalvm (2 classes)
diff --git a/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
b/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
index 62911a0..dbf17d9 100644
--- a/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
@@ -68,6 +68,7 @@
<exclude>org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService$*.class</exclude>
<exclude>org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.class</exclude>
<exclude>org/apache/skywalking/oap/server/core/logging/LoggingConfigWatcher.class</exclude>
+
<exclude>org/apache/skywalking/oap/server/core/alarm/HttpAlarmCallback.class</exclude>
</excludes>
</filter>
</filters>
diff --git
a/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/alarm/HttpAlarmCallback.java
b/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/alarm/HttpAlarmCallback.java
new file mode 100644
index 0000000..869c2b4
--- /dev/null
+++
b/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/alarm/HttpAlarmCallback.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.oap.server.core.alarm;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * GraalVM-compatible replacement: lazily initializes HttpClient to avoid
+ * build-time static initialization issues in native image.
+ */
+public abstract class HttpAlarmCallback implements AlarmCallback {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(HttpAlarmCallback.class);
+
+ private static volatile HttpClient HTTP_CLIENT;
+
+ private static HttpClient getHttpClient() {
+ if (HTTP_CLIENT == null) {
+ synchronized (HttpAlarmCallback.class) {
+ if (HTTP_CLIENT == null) {
+ LOGGER.info("Initializing HttpClient for alarm webhooks");
+ HTTP_CLIENT = HttpClient
+ .newBuilder()
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build();
+ }
+ }
+ }
+ return HTTP_CLIENT;
+ }
+
+ protected String post(
+ final URI uri,
+ final String body,
+ final Map<String, String> headers)
+ throws IOException, InterruptedException {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Sending alarm webhook POST to {}, body length: {}",
uri, body.length());
+ }
+ final var request = HttpRequest
+ .newBuilder()
+ .uri(uri)
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .header("Content-Type", "application/json")
+ .timeout(Duration.ofSeconds(12));
+ headers.forEach(request::header);
+
+ final var response = getHttpClient()
+ .send(request.build(), HttpResponse.BodyHandlers.ofString());
+
+ final var status = response.statusCode();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Alarm webhook response from {}: status={}", uri,
status);
+ }
+ if (status != 200 && status != 204) {
+ final var logger = LoggerFactory.getLogger(getClass());
+ logger.error(
+ "send to {} failure. Response code: {}, Response content:
{}",
+ uri, status, response.body()
+ );
+ }
+ return response.body();
+ }
+
+ /**
+ * Send alarm message if the settings not empty
+ */
+ public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
+ doAlarmCallback(alarmMessages, false);
+ }
+
+ /**
+ * Send alarm recovery message if the settings not empty
+ */
+ public void doAlarmRecovery(List<AlarmMessage> alarmRecoveryMessages)
throws Exception {
+ doAlarmCallback(alarmRecoveryMessages, true);
+ }
+
+ protected abstract void doAlarmCallback(List<AlarmMessage> alarmMessages,
boolean isRecovery) throws Exception;
+
+}