This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit aee6ef722008ac50b6527e623e6a0c8e11a2c8ce Author: Jiri Ondrusek <[email protected]> AuthorDate: Mon Aug 10 15:15:44 2026 +0200 Fix native image build deadlock in Saxon class initialization Saxon's SequenceType and MapType have mutually referencing static initializers: SequenceType.SINGLE_MAP references MapType.ANY_MAP_TYPE, which in turn references SequenceType.ANY_SEQUENCE. Initializing them from a single thread is safe, because class initialization is reentrant and ANY_SEQUENCE is assigned before SINGLE_MAP. SaxonProcessor registers all Saxon functions for reflection, and the native image builder performs those registrations from several analysis threads. When ArraySortBy and LoadXqueryModule are initialized concurrently, each thread ends up holding one class initialization lock while waiting for the other, and the image generator watchdog aborts the build. This is reproducible in the foundation-grouped integration test. Initialize the cycle from a GraalVM Feature before the analysis starts, so that it stays on a single thread. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- .../component/saxon/deployment/SaxonProcessor.java | 7 ++++ extensions/saxon/runtime/pom.xml | 5 +++ .../saxon/SaxonClassInitializationFeature.java | 43 ++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/extensions/saxon/deployment/src/main/java/org/apache/camel/quarkus/component/saxon/deployment/SaxonProcessor.java b/extensions/saxon/deployment/src/main/java/org/apache/camel/quarkus/component/saxon/deployment/SaxonProcessor.java index 469abbd3d7..f84027fa8a 100644 --- a/extensions/saxon/deployment/src/main/java/org/apache/camel/quarkus/component/saxon/deployment/SaxonProcessor.java +++ b/extensions/saxon/deployment/src/main/java/org/apache/camel/quarkus/component/saxon/deployment/SaxonProcessor.java @@ -25,11 +25,13 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import net.sf.saxon.Configuration; import net.sf.saxon.functions.SystemFunction; import net.sf.saxon.xpath.XPathFactoryImpl; +import org.apache.camel.quarkus.component.saxon.SaxonClassInitializationFeature; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.logging.Logger; @@ -74,6 +76,11 @@ class SaxonProcessor { deps.produce(new IndexDependencyBuildItem("net.sf.saxon", "Saxon-HE")); } + @BuildStep + NativeImageFeatureBuildItem classInitializationFeature() { + return new NativeImageFeatureBuildItem(SaxonClassInitializationFeature.class); + } + @BuildStep void runtimeInit(BuildProducer<RuntimeInitializedClassBuildItem> deps) { deps.produce(new RuntimeInitializedClassBuildItem("org.apache.hc.client5.http.impl.auth.NTLMEngineImpl")); diff --git a/extensions/saxon/runtime/pom.xml b/extensions/saxon/runtime/pom.xml index a0ca2fc8b4..be51ff5748 100644 --- a/extensions/saxon/runtime/pom.xml +++ b/extensions/saxon/runtime/pom.xml @@ -48,6 +48,11 @@ <groupId>org.apache.camel</groupId> <artifactId>camel-saxon</artifactId> </dependency> + <dependency> + <groupId>org.graalvm.sdk</groupId> + <artifactId>nativeimage</artifactId> + <scope>provided</scope> + </dependency> </dependencies> <build> diff --git a/extensions/saxon/runtime/src/main/java/org/apache/camel/quarkus/component/saxon/SaxonClassInitializationFeature.java b/extensions/saxon/runtime/src/main/java/org/apache/camel/quarkus/component/saxon/SaxonClassInitializationFeature.java new file mode 100644 index 0000000000..5633283383 --- /dev/null +++ b/extensions/saxon/runtime/src/main/java/org/apache/camel/quarkus/component/saxon/SaxonClassInitializationFeature.java @@ -0,0 +1,43 @@ +/* + * 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.camel.quarkus.component.saxon; + +import java.util.Objects; + +import net.sf.saxon.ma.map.MapType; +import net.sf.saxon.value.SequenceType; +import org.graalvm.nativeimage.hosted.Feature; + +/** + * Works around a class initialization deadlock in the native image builder. + * + * SequenceType and MapType have mutually referencing static initializers: SequenceType.SINGLE_MAP references + * MapType.ANY_MAP_TYPE, which in turn references SequenceType.ANY_SEQUENCE. Initializing them from a single thread is + * safe, because class initialization is reentrant and ANY_SEQUENCE is assigned before SINGLE_MAP. Initializing them + * concurrently deadlocks, as each thread holds one class initialization lock while waiting for the other. + * + * Saxon functions are registered for reflection in SaxonProcessor, and the builder performs those registrations on + * several analysis threads, which is enough to trigger the deadlock. Initializing the cycle here, before the analysis + * starts, keeps it on a single thread. + */ +public class SaxonClassInitializationFeature implements Feature { + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + Objects.requireNonNull(SequenceType.ANY_SEQUENCE); + Objects.requireNonNull(MapType.ANY_MAP_TYPE); + } +}
