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;
+
+}

Reply via email to