helidon icon indicating copy to clipboard operation
helidon copied to clipboard

UCP DataSource problem with GraalVM Native Image

Open dcoracle opened this issue 1 year ago • 1 comments

Attempting to perform a very simple Oracle Database sanity query (using UCP) but getting:

Caused by: java.lang.IllegalStateException: Cannot define class in native image. Class name: io.helidon.integrations.datasource.ucp.cdi.UniversalConnectionPoolAdapter$CommonDataSource$DataSource$ObjectFactory$PoolDataSource$Referenceable$Serializable$Wrapper$_$$_WeldClientProxy, original class: io.helidon.integrations.datasource.ucp.cdi.UCPBackedDataSourceExtension

This works when it is just a java application but native-image it doesn't work.

I used the GraalVM Tracing Agent and exercised the DAO which generated reflect-config.json that I added to source but still there is an issue (attached: reflect-config.json)

Environment Details

  • Helidon Version: 4.0.1
  • Helidon MP
  • JDK version: Java(TM) SE Runtime Environment Oracle GraalVM 21.0.1+12.1 (build 21.0.1+12-jvmci-23.1-b19)
  • OS: 14.1.2 on Apple Silicon (M1)
  • Docker version (if applicable): N/A

Problem Description

Steps to reproduce

  1. Add the following to pom.xml:
       <dependency>
            <groupId>io.helidon.integrations.cdi</groupId>
            <artifactId>helidon-integrations-cdi-datasource-ucp</artifactId>
        </dependency>
  1. Define following DAO (DS initialized via standard microprofile)
...
...

@ApplicationScoped
public class SupportDAO {
    @Inject
    @Named("abc")
    private DataSource abcDataSource;

    public String getSanity() throws RuntimeException{
        String check = null;

        try (Connection connection = this.abcDataSource.getConnection();
             PreparedStatement ps = connection.prepareStatement(Utils.getSqlQuery(Constants.SQL_FETCH_QRY));) {

            ResultSet rs = ps.executeQuery();
            while(rs.next()) {
                check = rs.getString(1);
            }
        }
        catch(Throwable e){
            e.printStackTrace();
        }

        return check;
    }

}

//Constants.SQL_FETCH_QRY is just --> SQL_FETCH_QRY("SELECT 'SANITY' from dual");

  1. Simply execute the above method from calling class

  2. Compile to native image via

mvn -Pnative-image clean package -DskipTests
  1. Full Stacktrace:
org.jboss.weld.exceptions.WeldException: WELD-001524: Unable to load proxy class for bean Configurator Bean [class io.helidon.integrations.datasource.ucp.cdi.UCPBackedDataSourceExtension, types: Referenceable, DataSource, Object, Serializable, ObjectFactory, Wrapper, PoolDataSourceImpl, CommonDataSource, PoolDataSource, UniversalConnectionPoolAdapter, qualifiers: @Default @Any @Named] with class class java.lang.Object
	at org.jboss.weld.bean.proxy.ProxyFactory.getProxyClass(ProxyFactory.java:453)
	at org.jboss.weld.bean.proxy.ProxyFactory.run(ProxyFactory.java:404)
	at org.jboss.weld.bean.proxy.ProxyFactory.create(ProxyFactory.java:396)
	at org.jboss.weld.bean.proxy.ClientProxyFactory.create(ClientProxyFactory.java:83)
	at org.jboss.weld.bean.proxy.ClientProxyProvider.createClientProxy(ClientProxyProvider.java:205)
	at org.jboss.weld.bean.proxy.ClientProxyProvider$CreateClientProxyForType.apply(ClientProxyProvider.java:85)
	at org.jboss.weld.bean.proxy.ClientProxyProvider$CreateClientProxyForType.apply(ClientProxyProvider.java:59)
	at org.jboss.weld.util.cache.ReentrantMapBackedComputingCache.lambda$new$0(ReentrantMapBackedComputingCache.java:55)
	at org.jboss.weld.util.LazyValueHolder$1.computeValue(LazyValueHolder.java:32)
	at org.jboss.weld.util.LazyValueHolder.get(LazyValueHolder.java:46)
	at org.jboss.weld.util.cache.ReentrantMapBackedComputingCache.getValue(ReentrantMapBackedComputingCache.java:72)
	at org.jboss.weld.util.cache.ReentrantMapBackedComputingCache.getCastValue(ReentrantMapBackedComputingCache.java:78)
	at org.jboss.weld.bean.proxy.ClientProxyProvider.getClientProxy(ClientProxyProvider.java:236)
	at org.jboss.weld.manager.BeanManagerImpl.getReference(BeanManagerImpl.java:676)
	at org.jboss.weld.manager.BeanManagerImpl.getInjectableReference(BeanManagerImpl.java:784)
	at org.jboss.weld.injection.FieldInjectionPoint.inject(FieldInjectionPoint.java:92)
	at org.jboss.weld.util.Beans.injectBoundFields(Beans.java:345)
	at org.jboss.weld.util.Beans.injectFieldsAndInitializers(Beans.java:356)
	at org.jboss.weld.injection.producer.ResourceInjector$1.proceed(ResourceInjector.java:69)
	at org.jboss.weld.injection.InjectionContextImpl.run(InjectionContextImpl.java:48)
	at org.jboss.weld.injection.producer.ResourceInjector.inject(ResourceInjector.java:71)
	at org.jboss.weld.injection.producer.BasicInjectionTarget.inject(BasicInjectionTarget.java:117)
	at org.jboss.weld.bean.ManagedBean.create(ManagedBean.java:161)
	at org.jboss.weld.contexts.AbstractContext.get(AbstractContext.java:96)
	at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.get(ContextualInstanceStrategy.java:100)
	at org.jboss.weld.bean.ContextualInstanceStrategy$ApplicationScopedContextualInstanceStrategy.get(ContextualInstanceStrategy.java:140)
	at org.jboss.weld.bean.ContextualInstance.get(ContextualInstance.java:50)
	at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:102)
	at org.jboss.weld.bean.proxy.ProxyMethodHandler.getInstance(ProxyMethodHandler.java:131)
	at com.oracle.oal.worklistplus.supportservice.dao.SupportDAO$Proxy$_$$_WeldClientProxy.getSanity(Unknown Source)
	at com.oracle.oal.worklistplus.supportservice.repository.SupportRepository.getSanity(SupportRepository.java:20)
	at com.oracle.oal.worklistplus.supportservice.repository.SupportRepository$Proxy$_$$_WeldClientProxy.getSanity(Unknown Source)
	at com.oracle.oal.worklistplus.supportservice.service.rest.SupportService.getRdbmsSanity(SupportService.java:62)
	at com.oracle.oal.worklistplus.supportservice.service.rest.SupportService$Proxy$_$$_WeldClientProxy.getRdbmsSanity(Unknown Source)
	at [email protected]/java.lang.reflect.Method.invoke(Method.java:580)
	at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:146)
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:189)
	at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176)
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:93)
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
	at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:261)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:240)
	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:697)
	at io.helidon.microprofile.server.JaxRsService.doHandle(JaxRsService.java:250)
	at io.helidon.microprofile.server.JaxRsService.lambda$handle$2(JaxRsService.java:198)
	at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
	at io.helidon.microprofile.server.JaxRsService.handle(JaxRsService.java:198)
	at io.helidon.webserver.http.HttpRouting$RoutingExecutor.doRoute(HttpRouting.java:668)
	at io.helidon.webserver.http.HttpRouting$RoutingExecutor.call(HttpRouting.java:627)
	at io.helidon.webserver.http.HttpRouting$RoutingExecutor.call(HttpRouting.java:605)
	at io.helidon.webserver.http.ErrorHandlers.runWithErrorHandling(ErrorHandlers.java:75)
	at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:121)
	at io.helidon.webserver.observe.metrics.MetricsFeature.lambda$configureVendorMetrics$2(MetricsFeature.java:90)
	at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:119)
	at io.helidon.webserver.security.SecurityContextFilter.filter(SecurityContextFilter.java:88)
	at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:119)
	at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
	at io.helidon.webserver.context.ContextRoutingFeature.filter(ContextRoutingFeature.java:50)
	at io.helidon.webserver.http.Filters$FilterChainImpl.proceed(Filters.java:119)
	at io.helidon.webserver.http.Filters.executeFilters(Filters.java:87)
	at io.helidon.webserver.http.Filters.lambda$filter$0(Filters.java:83)
	at io.helidon.webserver.http.ErrorHandlers.runWithErrorHandling(ErrorHandlers.java:75)
	at io.helidon.webserver.http.Filters.filter(Filters.java:83)
	at io.helidon.webserver.http.HttpRouting.route(HttpRouting.java:109)
	at io.helidon.webserver.http1.Http1Connection.route(Http1Connection.java:350)
	at io.helidon.webserver.http1.Http1Connection.handle(Http1Connection.java:187)
	at io.helidon.webserver.ConnectionHandler.run(ConnectionHandler.java:165)
	at io.helidon.common.task.InterruptableTask.call(InterruptableTask.java:47)
	at io.helidon.webserver.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:239)
	at [email protected]/java.lang.Thread.runWith(Thread.java:1596)
	at [email protected]/java.lang.VirtualThread.run(VirtualThread.java:309)
	at [email protected]/java.lang.VirtualThread$VThreadContinuation$1.run(VirtualThread.java:190)
Caused by: java.lang.IllegalStateException: Cannot define class in native image. Class name: io.helidon.integrations.datasource.ucp.cdi.UniversalConnectionPoolAdapter$CommonDataSource$DataSource$ObjectFactory$PoolDataSource$Referenceable$Serializable$Wrapper$_$$_WeldClientProxy, original class: io.helidon.integrations.datasource.ucp.cdi.UCPBackedDataSourceExtension
	at io.helidon.microprofile.cdi.HelidonProxyServices.defineClassPrivateLookup(HelidonProxyServices.java:114)
	at io.helidon.microprofile.cdi.HelidonProxyServices.defineClass(HelidonProxyServices.java:93)
	at org.jboss.weld.bean.proxy.ProxyFactory.toClass(ProxyFactory.java:1073)
	at org.jboss.weld.bean.proxy.ProxyFactory.createProxyClass(ProxyFactory.java:628)
	at org.jboss.weld.bean.proxy.ProxyFactory.getProxyClass(ProxyFactory.java:445)
	... 80 more
2024.01.05 11:47:47 SEVERE com.oracle.oal.worklistplus.supportservice.service.rest.SupportService VirtualThread[#77,[0x344cc5d4 0x4e92ff76] WebServer socket]/runnable@ForkJoinPool-5-worker-2[", , , , "]: WELD-001524: Unable to load proxy class for bean Configurator Bean [class io.helidon.integrations.datasource.ucp.cdi.UCPBackedDataSourceExtension, types: Referenceable, DataSource, Object, Serializable, ObjectFactory, Wrapper, PoolDataSourceImpl, CommonDataSource, PoolDataSource, UniversalConnectionPoolAdapter, qualifiers: @Default @Any @Named] with class class java.lang.Object

dcoracle avatar Jan 05 '24 17:01 dcoracle

Is there an ETA on this fix?

dcoracle avatar Jan 30 '24 16:01 dcoracle

I've boiled down your case to a simple command-line application that I made using helidon init (and then removed everything that wasn't necessary, and used H2 as the backing database for simplicity). I get native image errors, but not the one you have above. I'll continue to look at this.

The relevant source code:

package com.example.ucpni;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@ApplicationScoped
public class SupportDAO {

    @Inject
    @Named("abc")
    private DataSource abcDataSource;

    public String getSanity() throws SQLException {
        try (Connection connection = this.abcDataSource.getConnection();
             PreparedStatement ps = connection.prepareStatement("SELECT 'SANITY'");
             ResultSet rs = ps.executeQuery()) {
            rs.next();
            return rs.getString(1);
        }
    }

    private static void onStartup(@Observes @Initialized(ApplicationScoped.class) Object event, SupportDAO dao) throws SQLException {
        System.out.println("*** " + dao.getSanity());
    }

    public static void main(String[] args) {
        io.helidon.microprofile.cdi.Main.main(args);
    }
}

The relevant pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>io.helidon.applications</groupId>
        <artifactId>helidon-mp</artifactId>
        <version>4.0.1</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>ucpni</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.helidon.microprofile.cdi</groupId>
            <artifactId>helidon-microprofile-cdi</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.inject</groupId>
            <artifactId>jakarta.inject-api</artifactId>
        </dependency>

        <dependency>
            <groupId>io.smallrye</groupId>
            <artifactId>jandex</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.helidon.integrations.cdi</groupId>
            <artifactId>helidon-integrations-cdi-datasource-ucp</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.helidon.microprofile.testing</groupId>
            <artifactId>helidon-microprofile-testing-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-libs</id>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>io.smallrye</groupId>
                <artifactId>jandex-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>make-index</id>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The src/main/resources/META-INF/microprofile-config.properties file in its entirety:

oracle.ucp.jdbc.PoolDataSource.abc.URL=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
oracle.ucp.jdbc.PoolDataSource.abc.connectionFactoryClassName=org.h2.jdbcx.JdbcDataSource
oracle.ucp.jdbc.PoolDataSource.abc.user=sa
oracle.ucp.jdbc.PoolDataSource.abc.password=

ljnelson avatar Feb 27 '24 22:02 ljnelson

When run from the command line, this works:

% JAVA_HOME=$(/usr/libexec/java_home -v21) java -jar target/ucpni.jar 
There is no Helidon logging implementation on classpath, skipping log configuration.
Feb 27, 2024 2:37:04 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 5.1.1 (SP2)
Feb 27, 2024 2:37:05 PM org.jboss.weld.environment.deployment.discovery.DiscoveryStrategyFactory create
INFO: WELD-ENV-000020: Using jandex for bean discovery
Feb 27, 2024 2:37:05 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
*** SANITY
Feb 27, 2024 2:37:05 PM io.helidon.common.features.HelidonFeatures features
INFO: Helidon MP 4.0.1 features: [CDI]
Feb 27, 2024 2:37:05 PM io.helidon.microprofile.cdi.HelidonContainerImpl$CdiShutdownHook run
INFO: Shutdown requested by JVM shutting down
Feb 27, 2024 2:37:05 PM io.helidon.microprofile.cdi.HelidonContainerImpl$HelidonCdi close
INFO: WELD-ENV-002001: Weld SE container b20d7324-bc83-4ee7-9ad2-b85183a29fba shut down
Feb 27, 2024 2:37:05 PM io.helidon.microprofile.cdi.HelidonContainerImpl$CdiShutdownHook run
INFO: Shutdown finished

ljnelson avatar Feb 27 '24 22:02 ljnelson

When run with the native-image profile, I get this (stand back):

[INFO] --- native-maven-plugin:0.9.27:compile (build-native-image) @ ucpni ---
[INFO] Found GraalVM installation from GRAALVM_HOME variable.
[WARNING] Ignoring ImageClasspath Entry 'com.oracle.database.jdbc:ojdbc8-production:pom:21.4.0.0:runtime' with unsupported type 'pom'
[WARNING] Properties file at 'jar:file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ojdbc8/21.4.0.0/ojdbc8-21.4.0.0.jar!/META-INF/native-image/native-image.properties' does not match the recommended 'META-INF/native-image/com.oracle.database.jdbc/ojdbc8/native-image.properties' layout.
[WARNING] Properties file at 'jar:file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ucp/21.4.0.0/ucp-21.4.0.0.jar!/META-INF/native-image/native-image.properties' does not match the recommended 'META-INF/native-image/com.oracle.database.jdbc/ucp/native-image.properties' layout.
[INFO] Executing: /Library/Java/JavaVirtualMachines/graalvm-latest/Contents/Home/bin/native-image @target/tmp/native-image-7893639051740601106.args io.helidon.Main
Warning: Using a deprecated option --allow-incomplete-classpath from 'META-INF/native-image/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ojdbc8/21.4.0.0/ojdbc8-21.4.0.0.jar'. Allowing an incomplete classpath is now the default. Use --link-at-build-time to report linking errors at image build time for a class or package.
Warning: Using a deprecated option --allow-incomplete-classpath from 'META-INF/native-image/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ucp/21.4.0.0/ucp-21.4.0.0.jar'. Allowing an incomplete classpath is now the default. Use --link-at-build-time to report linking errors at image build time for a class or package.
Warning: The option '-H:EnableURLProtocols=http' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: The option '-H:ResourceConfigurationResources=META-INF/native-image/com.oracle.database.xml/xmlparserv2/resource-config.json' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: The option '-H:EnableURLProtocols=https' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: The option '-H:ReflectionConfigurationResources=META-INF/native-image/com.oracle.database.xml/xmlparserv2/reflect-config.json' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: Please re-evaluate whether any experimental option is required, and either remove or unlock it. The build output lists all active experimental options, including where they come from and possible alternatives. If you think an experimental option should be considered as stable, please file an issue.
Warning: Option 'EnableAllSecurityServices' is deprecated and might be removed in a future release. Please refer to the GraalVM release notes.
Warning: Option 'EnableAllSecurityServices' is deprecated and might be removed in a future release. Please refer to the GraalVM release notes.
========================================================================================================================
GraalVM Native Image: Generating 'ucpni' (executable)...
========================================================================================================================
There is no Helidon logging implementation on classpath, skipping log configuration.
Warning: Feature class oracle.nativeimage.NativeImageFeature is annotated with the deprecated annotation @AutomaticFeature. Support for this annotation will be removed in a future version of GraalVM. Applications should register a feature using the option --features=oracle.nativeimage.NativeImageFeature
Warning: Feature class oracle.nativeimage.CharacterSetFeature is annotated with the deprecated annotation @AutomaticFeature. Support for this annotation will be removed in a future version of GraalVM. Applications should register a feature using the option --features=oracle.nativeimage.CharacterSetFeature
[1/8] Initializing...                                                                                    (4.9s @ 0.20GB)
 Java version: 21+35, vendor version: Oracle GraalVM 21+35.1
 Graal compiler: optimization level: 2, target machine: armv8-a, PGO: off
 C compiler: cc (apple, arm64, 15.0.0)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 5 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
 - io.helidon.integrations.graal.mp.nativeimage.extension.HelidonMpFeature
 - io.helidon.integrations.graal.nativeimage.extension.HelidonReflectionFeature
 - oracle.nativeimage.CharacterSetFeature
 - oracle.nativeimage.NativeImageFeature
------------------------------------------------------------------------------------------------------------------------
 3 experimental option(s) unlocked:
 - '-H:EnableURLProtocols' (alternative API option(s): --enable-http, --enable-https, --enable-http, --enable-https; origin(s): 'META-INF/native-image/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ojdbc8/21.4.0.0/ojdbc8-21.4.0.0.jar', 'META-INF/native-image/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ojdbc8/21.4.0.0/ojdbc8-21.4.0.0.jar', 'META-INF/native-image/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ucp/21.4.0.0/ucp-21.4.0.0.jar', 'META-INF/native-image/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/jdbc/ucp/21.4.0.0/ucp-21.4.0.0.jar')
 - '-H:ResourceConfigurationResources' (origin(s): 'META-INF/native-image/com.oracle.database.xml/xmlparserv2/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/xml/xmlparserv2/21.4.0.0/xmlparserv2-21.4.0.0.jar')
 - '-H:ReflectionConfigurationResources' (origin(s): 'META-INF/native-image/com.oracle.database.xml/xmlparserv2/native-image.properties' in 'file:///Users/lairdnelson/.m2/repository/com/oracle/database/xml/xmlparserv2/21.4.0.0/xmlparserv2-21.4.0.0.jar')
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 24.18GB of memory (75.6% of 32.00GB system memory, determined at start)
 - 8 thread(s) (100.0% of 8 available processor(s), determined at start)
Feb 27, 2024 2:40:04 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 5.1.1 (SP2)
Feb 27, 2024 2:40:04 PM org.jboss.weld.environment.deployment.discovery.DiscoveryStrategyFactory create
INFO: WELD-ENV-000020: Using jandex for bean discovery
Feb 27, 2024 2:40:04 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
[2/8] Performing analysis...  [*]                                                                       (61.5s @ 2.97GB)
   17,608 reachable types   (89.8% of   19,600 total)
   48,280 reachable fields  (73.1% of   66,027 total)
  111,731 reachable methods (65.7% of  170,110 total)
    5,367 types, 10,007 fields, and 7,125 methods registered for reflection
        1 native library: -framework CoreServices

Error: Classes that should be initialized at run time got initialized during image building:
 io.helidon.config.PropertiesConfigParser was unintentionally initialized at build time. To see why io.helidon.config.PropertiesConfigParser got initialized use --trace-class-initialization=io.helidon.config.PropertiesConfigParser
io.helidon.config.ConfigSourceRuntimeImpl was unintentionally initialized at build time. To see why io.helidon.config.ConfigSourceRuntimeImpl got initialized use --trace-class-initialization=io.helidon.config.ConfigSourceRuntimeImpl
io.helidon.config.mp.MpConfigImpl was unintentionally initialized at build time. To see why io.helidon.config.mp.MpConfigImpl got initialized use --trace-class-initialization=io.helidon.config.mp.MpConfigImpl
io.helidon.config.spi.SpiHelper$EmptyObjectNodeHolder was unintentionally initialized at build time. To see why io.helidon.config.spi.SpiHelper$EmptyObjectNodeHolder got initialized use --trace-class-initialization=io.helidon.config.spi.SpiHelper$EmptyObjectNodeHolder
io.helidon.config.ValueResolvingFilter was unintentionally initialized at build time. To see why io.helidon.config.ValueResolvingFilter got initialized use --trace-class-initialization=io.helidon.config.ValueResolvingFilter
io.helidon.config.BuilderImpl$EmptyConfigHolder was unintentionally initialized at build time. To see why io.helidon.config.BuilderImpl$EmptyConfigHolder got initialized use --trace-class-initialization=io.helidon.config.BuilderImpl$EmptyConfigHolder
io.helidon.config.yaml.YamlConfigParser was unintentionally initialized at build time. To see why io.helidon.config.yaml.YamlConfigParser got initialized use --trace-class-initialization=io.helidon.config.yaml.YamlConfigParser
io.helidon.config.MetaConfig was unintentionally initialized at build time. To see why io.helidon.config.MetaConfig got initialized use --trace-class-initialization=io.helidon.config.MetaConfig
io.helidon.config.mp.MpEnvironmentVariablesSource was unintentionally initialized at build time. To see why io.helidon.config.mp.MpEnvironmentVariablesSource got initialized use --trace-class-initialization=io.helidon.config.mp.MpEnvironmentVariablesSource
io.helidon.config.ConfigMapperManager was unintentionally initialized at build time. To see why io.helidon.config.ConfigMapperManager got initialized use --trace-class-initialization=io.helidon.config.ConfigMapperManager
io.helidon.config.ConfigFactory$1 was unintentionally initialized at build time. To see why io.helidon.config.ConfigFactory$1 got initialized use --trace-class-initialization=io.helidon.config.ConfigFactory$1
io.helidon.config.ConfigMappers was unintentionally initialized at build time. To see why io.helidon.config.ConfigMappers got initialized use --trace-class-initialization=io.helidon.config.ConfigMappers
io.helidon.config.mp.MpMetaConfig was unintentionally initialized at build time. To see why io.helidon.config.mp.MpMetaConfig got initialized use --trace-class-initialization=io.helidon.config.mp.MpMetaConfig
io.helidon.config.OverrideSourceRuntime was unintentionally initialized at build time. To see why io.helidon.config.OverrideSourceRuntime got initialized use --trace-class-initialization=io.helidon.config.OverrideSourceRuntime
io.helidon.config.mp.MpConfigBuilder was unintentionally initialized at build time. To see why io.helidon.config.mp.MpConfigBuilder got initialized use --trace-class-initialization=io.helidon.config.mp.MpConfigBuilder
io.helidon.config.mp.SeConfig was unintentionally initialized at build time. To see why io.helidon.config.mp.SeConfig got initialized use --trace-class-initialization=io.helidon.config.mp.SeConfig
io.helidon.config.MetaConfigFinder was unintentionally initialized at build time. To see why io.helidon.config.MetaConfigFinder got initialized use --trace-class-initialization=io.helidon.config.MetaConfigFinder
io.helidon.config.ProviderImpl was unintentionally initialized at build time. To see why io.helidon.config.ProviderImpl got initialized use --trace-class-initialization=io.helidon.config.ProviderImpl
To see how the classes got initialized, use --trace-class-initialization=io.helidon.config.PropertiesConfigParser,io.helidon.config.ConfigSourceRuntimeImpl,io.helidon.config.mp.MpConfigImpl,io.helidon.config.spi.SpiHelper$EmptyObjectNodeHolder,io.helidon.config.ValueResolvingFilter,io.helidon.config.BuilderImpl$EmptyConfigHolder,io.helidon.config.yaml.YamlConfigParser,io.helidon.config.MetaConfig,io.helidon.config.mp.MpEnvironmentVariablesSource,io.helidon.config.ConfigMapperManager,io.helidon.config.ConfigFactory$1,io.helidon.config.ConfigMappers,io.helidon.config.mp.MpMetaConfig,io.helidon.config.OverrideSourceRuntime,io.helidon.config.mp.MpConfigBuilder,io.helidon.config.mp.SeConfig,io.helidon.config.MetaConfigFinder,io.helidon.config.ProviderImpl
------------------------------------------------------------------------------------------------------------------------
                        5.7s (8.4% of total time) in 65 GCs | Peak RSS: 5.31GB | CPU load: 5.79
========================================================================================================================
Finished generating 'ucpni' in 1m 7s.

ljnelson avatar Feb 27 '24 22:02 ljnelson

I am not at all an expert on native image and I am not entirely sure what I am staring at but I will continue to look at this error that I've reported above; perhaps it will somehow let me arrive at the error that you reported.

ljnelson avatar Feb 27 '24 22:02 ljnelson

Additionally, if I add -DbuildArgs=--trace-class-initialization=io.helidon.config.PropertiesConfigParser (for example) to the command line, native image building fails due to a problem with oracle.nativeimage.NativeImageFeature, which I think must be in one of the Oracle database jars somewhere:

Error: Feature defined by oracle.nativeimage.NativeImageFeature unexpectedly failed with a(n) java.lang.IllegalAccessError. Please report this problem to the authors of oracle.nativeimage.NativeImageFeature.

ljnelson avatar Feb 27 '24 23:02 ljnelson

Progress update: I've pared the example down even further to a simple CDI program that does not use or include anything database-related at all. I still get the same error. However, I can make my ruthlessly pared-down example build under native image if I bring in all of our MicroProfile-related libraries (via helidon-microprofile). That is concerning to me because my project doesn't need them so I shouldn't have to bring them in to make native image compilation work.

My next steps will be to figure out which of the many libraries brought in from helidon-microprofile that are unneeded by my project is somehow responsible for the native image build working. I will be whittling them down one by one so this may take a while.

ljnelson avatar Feb 28 '24 01:02 ljnelson

I have managed to figure out which libraries of ours contribute which command line arguments to the native image build process (for example, helidon-microprofile-config contributes command line arguments that are (surprisingly to me) germane to non-MicroProfile-related Helidon classes among others) and can now reproduce your error with my minimal application. The immediate solution for you will almost certainly be a META-INF/helidon/native-image/weld-proxies.json classpath resource containing specific contents. It should live in our codebase, but currently does not.

ljnelson avatar Feb 28 '24 03:02 ljnelson

While tracing class initialization, I also discovered that the Oracle database driver has a native image Feature in it that crashes if --trace-class-initialization=something is specified. The stack includes:

Caused by: java.lang.IllegalAccessError: class oracle.nativeimage.NativeImageFeature (in unnamed module @0x782be4eb) cannot access class com.oracle.svm.core.configure.ResourcesRegistry (in module org.graalvm.nativeimage.builder) because module org.graalvm.nativeimage.builder does not export com.oracle.svm.core.configure to unnamed module @0x782be4eb
	at oracle.nativeimage.NativeImageFeature.getResourceRegistry(NativeImageFeature.java:123)
	at oracle.nativeimage.NativeImageFeature.beforeAnalysis(NativeImageFeature.java:69)

To work around that problem (which I think is with the GraalVM tooling itself) while invoking mvn, you can add -DjvmArgs="--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.configure=ALL-UNNAMED" to the command line. That lets you trace class initialization and get more visibility on the whole process.

ljnelson avatar Feb 28 '24 03:02 ljnelson

Making headway. If there is a classpath resource named META-INF/helidon/native-image/weld-proxies.json that is, exactly:

[
    {
        "bean-class": "io.helidon.integrations.datasource.ucp.cdi.UCPBackedDataSourceExtension",
        "ifaces": [
            "java.io.Serializable",
            "java.sql.Wrapper",
            "javax.naming.Referenceable",
            "javax.naming.spi.ObjectFactory",
            "javax.sql.CommonDataSource",
            "javax.sql.DataSource",
            "oracle.ucp.jdbc.PoolDataSource",
            "oracle.ucp.UniversalConnectionPoolAdapter"
        ]
    }
]

…we move on to the next set of errors. Note that in the above only interfaces may appear, except for the bean-class key, which, in this case, must be the class name of the portable extension defining the synthetic bean. (Why PoolDataSourceImpl doesn't appear (and, indeed, causes errors if it does appear) is not clear to me. It is also not clear to me how to additionally specify, in a similar manner, the required things for a synthetic bean that is also added of type PoolXADataSourceImpl. Finally it is not clear why this set of bean types must be interfaces, particularly since a bean's bean types may include its superclass reflexively.)

The next set of errors is much more promising, containing stack snippets such as:

Caused by: java.sql.SQLException: Unable to create factory class instance with provided factory class name: java.lang.ClassNotFoundException: 
	at oracle.ucp.util.UCPErrorHandler.newSQLException(UCPErrorHandler.java:499)
	at oracle.ucp.util.UCPErrorHandler.throwSQLException(UCPErrorHandler.java:176)
	at oracle.ucp.jdbc.PoolDataSourceImpl.initConnectionFactory(PoolDataSourceImpl.java:3030)
	at oracle.ucp.jdbc.PoolDataSourceImpl.createUniversalConnectionPool(PoolDataSourceImpl.java:969)
	... 36 more
Caused by: java.lang.ClassNotFoundException: 
	at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122)
	at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86)
	at [email protected]/java.lang.Class.forName(DynamicHub.java:1356)
	at [email protected]/java.lang.Class.forName(DynamicHub.java:1345)
	at oracle.ucp.util.Util.getClassForName(Util.java:315)
	at oracle.ucp.jdbc.PoolDataSourceImpl.initConnectionFactory(PoolDataSourceImpl.java:3021)

ljnelson avatar Feb 28 '24 06:02 ljnelson

I believe I have a working weld-proxies.json file. I am now having a devil of a time getting org.h2.jdbcx.JdbcDataSource registered for reflection, following the guide here: https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/Reflection/#manual-configuration

I, like you, have also attempted the native image tracing agent, but its output is so verbose and redundant that it is borderline useless (there should be no need, for example, to register byte[].class for reflection, but it appears to do so).

Additionally, since native image compilation itself is so slow and cumbersome and error-prone, this will continue to take me quite some time because the round trip is several minutes. Thanks for your patience.

The root issue around all this is (minus the various bugs in all the various tools along the way): the JDBC specification mandates reflection for DataSource configuration. That, plus the dynamic and reflective nature of CDI, makes the particular combination of CDI, DataSource-using technologies, and the reflective nature of connection pools a very poor choice for native image. Native image is of course best used for tiny stateless functions (think AWS Lambda or OCI's Fn or similar), and is ill-suited for an application such as this (and for most long-lived applications, really).

I will, of course, continue to get to the bottom of this so at least we can say that with appropriate configuration heroics you can produce a (probably large) native image for this case should you wish.

ljnelson avatar Feb 28 '24 19:02 ljnelson

Thank you for your valiant effort Laird. I appreciate it. In terms of use case, I think when this is working, we will have the best of both worlds. For our topology, we typically start with a minimum set of pods (i.e. 3 pods) and use HPA to scale up to however much we need to in our capacity plan. Without DataSource Connection Pooling, creating connections on demand with ephemeral containers will be very expensive, resulting in increased response times.

By doing this, the minimum set of long-lived pods will be ready to handle requests immediately (and quickly), and using HPA rules, we can take care of spikes in traffic via ephemeral native-image pods, which by nature should startup very quickly to handle those requests (at least that is our goal). DataSource within these ephemeral native-image pods will also help pool those connections to serve the spike until the HPA no longer needs it.

dcoracle avatar Feb 28 '24 19:02 dcoracle

I guess my point is: you can, of course, use UCP without CDI (or Helidon, for that matter). If your only requirement is connection pooling you could look at that. (Obviously you give up a lot of other features, but if your use case is tightly focused enough….)

Another (IMHO better) option to investigate that would keep you out of the native image ecosystem would be simple class data sharing. This would let you benefit from the superior performance of the JVM while reducing startup time.

I have your project working. Once I clean it up I will post it here.

ljnelson avatar Feb 28 '24 20:02 ljnelson

Thanks for the tips. I will take a look at it

dcoracle avatar Feb 28 '24 20:02 dcoracle

I have attached a very, very pared down project that is suitable only for this example and warranted for no purpose, including this one 😄 : ucpni.tar.gz

The project starts a Helidon-enhanced CDI container, prints out your sanity check, sourced in this case from the H2 database by way of the UCP, to STDOUT, and exits.

The project's native image-related configuration is a mix of stuff that should be in UCP itself, the Oracle driver, our codebase and your project. I've heavily commented the files involved so you know which is which, and which things are flaws in the toolchain and which are information that you must supply and/or change as appropriate.

(A "real" Helidon project will of course not simply be a CDI container that starts and exits.)

To build: mvn clean install; run java -jar target/ucpni.jar

To build a native image executable: mvn clean package -Pnative-image -DskipTests; run ./target/ucpni

I will say again that using native image in this particular use case in combination with CDI and the UCP is not a very good fit, but at least now we know how to make it work.

Several issues may come out of this, chiefly concerned with where to locate all the various bits of Helidon and native image configuration that are required, but this should at least give you a path forward. I'm going to leave this issue open so that I can harvest it for other more focused issues.

  • https://github.com/helidon-io/helidon/issues/8436

ljnelson avatar Feb 28 '24 21:02 ljnelson

FYI, if you use mvn -Pjlink-image clean package -DskipTests it will generate a custom JRE runtime for you plus a CDS archive (by default). More information in the Helidon 4 docs

barchetta avatar Feb 28 '24 21:02 barchetta

This works. Thank you!

dcoracle avatar Mar 01 '24 18:03 dcoracle

Hi. As I am trying to convert my local project to a containerized microservice, I am having issues with injecting MP Config DataSource values via environment-variables in meta-config.yaml:

sources:
  - type: "environment-variables"

I am getting:

Caused by: oracle.ucp.UniversalConnectionPoolException: Error during pool creation in Universal Connection Pool Manager MBean: oracle.ucp.UniversalConnectionPoolException: Error during pool creation in Universal Connection Pool Manager: java.sql.SQLException: Invalid Universal Connection Pool configuration: java.sql.SQLException: Unable to set the URL: java.lang.IllegalArgumentException: attempt to set an empty or null database URL in a connection factory

Even though I've defined it in the environment (using bash):

javax.sql.DataSource.wlp.URL=jdbc:oracle:thin:@HOST:PORT/SERVICENAME
javax.sql.DataSource.wlp.connectionFactoryName=oracle.jdbc.pool.OracleDataSource
javax.sql.DataSource.wlp.user=user
javax.sql.DataSource.wlp.password=password

and within my DAO, I have it injected like so:

    @Inject
    @Named("wlp")
    private DataSource wlpDataSource;

This works if I created a local.yaml file and referencing it that way but for some reason I can't get it to work when using type: "environment-variables"

Finally, the above works in a regular java application, just not when I compile to native-image

dcoracle avatar Mar 06 '24 04:03 dcoracle

If you have a problem with native image as it relates to configuration (not to UCP specifically), please kindly open another issue. Thanks. I will say yet again that your use case, particularly since you are containerizing your project, and not developing a stateless function, is particularly ill-suited for native image.

ljnelson avatar Mar 06 '24 04:03 ljnelson

I was thinking it was a general MP Config Issue, and filing a new issue but I added the following to the test:

    @Inject
    @ConfigProperty(name = "POD_ID")
    private String POD_ID;

and native-image was able to display that value.

ok. I will take your advice and pivot as a failed experiment. It's getting too painful ;). I was looking at this wiki page: https://github.com/helidon-io/helidon/wiki/Native-image and see Hikari Data Source as "should work". Should I try this route? This page is pretty old so I'm not sure what the status of these are.

Just so you have a bigger picture of this, we have approximately 20(ish) microservices (with total of 50 replicas, some with HPA enabled workloads so it's semi-dynamic) developed over the past 3 years with our tech stack containing mainly of Helidon MP, Microstream (EclipseStore) for in-memory cache, SOAP/REST integrations, Kafka, and an Oracle DB (following DDD design principles -> so each entity is at having some connection to Oracle DB. At least for now). I felt that we were ready to convert/port at least some of those microservices in the hopes we could reduce compute resources as well as reduce HPA bootstrap timings.

dcoracle avatar Mar 06 '24 05:03 dcoracle

You will be able to accomplish your goal, and certainly without native image. A Java microservice certainly does not need to be a native-image-generated executable; indeed it normally is better off without native image.

As mentioned several times above, getting native image to work with things that require reflection in order to fulfill their specifications' requirements is a tricky business. Here, for example, is the (currently? maybe?) "authoritative" "reachability metadata" maintained by "the community" for HikariCP (but only version 5.0.1, apparently): https://github.com/oracle/graalvm-reachability-metadata/tree/master/metadata/com.zaxxer/HikariCP/5.0.1 Why don't we "use" this? Because, among other reasons, the tool that uses it is not even to version 1.0.0 yet.

Part of the issue is you sometimes have to account for reflection details within the JDK itself, and these, of course, are subject to change. See, for example: https://github.com/openjdk/jdk/blob/jdk-21%2B35/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java#L306 and https://github.com/openjdk/jdk/blob/jdk-21%2B35/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java#L2089-L2090.

ljnelson avatar Mar 06 '24 05:03 ljnelson

ok. and thanks for all the links. I will review them. Another thing I also tried out is the JFR support in GraalVM and sadly, I feel this is lacking quite a bit of features that I am used to. It's just not there yet. Overall, it is very interesting tech, but probably at this stage of maturity, I wouldn't feel comfortable pushing this into a production environment, due to a lot of unforeseen problems we might see during runtime

dcoracle avatar Mar 06 '24 05:03 dcoracle

Closing in favor of #8436.

ljnelson avatar Jul 24 '24 23:07 ljnelson