Graal: Unable to use HttpClient in a Graal native image because of com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces

Created on 2 Mar 2019  路  5Comments  路  Source: oracle/graal

I am deploying a native image as an AWS lambda function using a custom runtime. This Java application uses Amazon SDK to access DynamoDB. The problem is when it initializes the HttpClient (Apache HttpClient underneath) it ends up throwing the following exception:

Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:109)
at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:112)
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:50)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
at com.amazonaws.http.conn.ClientConnectionManagerFactory.wrap(ClientConnectionManagerFactory.java:54)
at com.amazonaws.http.apache.client.impl.ApacheHttpClientFactory.create(ApacheHttpClientFactory.java:56)
at com.amazonaws.http.apache.client.impl.ApacheHttpClientFactory.create(ApacheHttpClientFactory.java:38)
at com.amazonaws.http.AmazonHttpClient.<init>(AmazonHttpClient.java:324)
at com.amazonaws.http.AmazonHttpClient.<init>(AmazonHttpClient.java:308)
at com.amazonaws.AmazonWebServiceClient.<init>(AmazonWebServiceClient.java:194)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.<init>(AmazonDynamoDBClient.java:366)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.<init>(AmazonDynamoDBClient.java:352)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder.build(AmazonDynamoDBClientBuilder.java:109)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder.build(AmazonDynamoDBClientBuilder.java:30)
at com.amazonaws.client.builder.AwsSyncClientBuilder.build(AwsSyncClientBuilder.java:46)
at com.codependent.micronaut.awslambda.configuration.DynamoDbConfiguration.dynamoDbClient(DynamoDbConfiguration.kt:18)
at com.codependent.micronaut.awslambda.configuration.$DynamoDbConfiguration$DynamoDbClientDefinition.build(Unknown Source)
at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1385)

A project to verify this behaviour can be found here: https://github.com/codependent/graal-app

Steps to reproduce:

  1. export AWS_DEFAULT_REGION=eu-central-1
  2. docker build . -t graal-app
  3. ./sam-local.sh

Then access http://127.0.0.1:3000/ping

The faulty behaviour has been extracted into the constructor of ExampleController:

public ExampleController() {
        //this.httpClient = httpClientFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
        LOG.info("init");
        final ConnectionManagerFactory<HttpClientConnectionManager> cmFactory = new ApacheConnectionManagerFactory();
        final HttpClientConnectionManager cm = cmFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
        ClientConnectionManagerFactory.wrap(cm);
    }

I tried to follow the exception's message adding:

-H:DynamicProxyConfigurationFiles="org.apache.http.conn.HttpClientConnectionManager,org.apache.http.pool.ConnPoolControl,com.amazonaws.http.conn.Wrapped" \

into the build-native-image.sh script but when I execute the docker build I get this error:

Writing reflect.json file to destination: build/reflect.json
Error: Invalid Path entry org.apache.http.conn.HttpClientConnectionManager
Caused by: java.nio.file.NoSuchFileException: /home/application/org.apache.http.conn.HttpClientConnectionManager
native-image

Most helpful comment

The proxy is defined by all 3 interfaces, so the proxy config file should contain:

[
  ["org.apache.http.conn.HttpClientConnectionManager", "org.apache.http.pool.ConnPoolControl", "com.amazonaws.http.conn.Wrapped"]
]

All 5 comments

The -H:DynamicProxyConfigurationFiles option accepts JSON files whose structure is an array of arrays of fully qualified interface names, not the array of interfaces directly. Please read the dynamic proxy documentation for more details and examples.

I'm afraid I'm missing something:

I created a dynamic-proxies.json file with this content:

[
  ["org.apache.http.conn.HttpClientConnectionManager"],
  ["org.apache.http.pool.ConnPoolControl"],
  ["com.amazonaws.http.conn.Wrapped"]
]

Then I modified the build-native-image.sh script:

...
-H:DynamicProxyConfigurationFiles="dynamic-proxies.json" \
...

But I keep getting the same error:

Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
        at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:109)
        at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:112)
        at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:50)
        at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
        at com.amazonaws.http.conn.ClientConnectionManagerFactory.wrap(ClientConnectionManagerFactory.java:54)
        at graal.app.ExampleController.<init>(ExampleController.java:46)
        at graal.app.$ExampleControllerDefinition.build(Unknown Source)
        at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1385)
        ... 38 more

The proxy is defined by all 3 interfaces, so the proxy config file should contain:

[
  ["org.apache.http.conn.HttpClientConnectionManager", "org.apache.http.pool.ConnPoolControl", "com.amazonaws.http.conn.Wrapped"]
]

My bad, it's working now. Thanks @cstancu!

Now I finally see the difference between @codependent code and @cstancu 馃憤 One is [[1][2][3], and the second is [[1,2,3]].

I understood the reason:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

The method takes a list of interfaces... Hence a list of lists.

In my situation (AWS), it was this method in software.amazon.awssdk.http.apache.internal.conn.:

public static HttpClientConnectionManager wrap(HttpClientConnectionManager orig) {
        if (orig instanceof Wrapped) {
            throw new IllegalArgumentException();
        }
        Class<?>[] interfaces;
        if (orig instanceof ConnPoolControl) {
            interfaces = new Class<?>[]{
                    HttpClientConnectionManager.class,
                    ConnPoolControl.class,
                    Wrapped.class};
        } else {
            interfaces = new Class<?>[]{
                    HttpClientConnectionManager.class,
                    Wrapped.class
            };
        }
        return (HttpClientConnectionManager) Proxy.newProxyInstance(
                // https://github.com/aws/aws-sdk-java/pull/48#issuecomment-29454423
                ClientConnectionManagerFactory.class.getClassLoader(),
                interfaces,
                new Handler(orig));
    }

so I did the following for dynamic-proxies.json:

[
  ["org.apache.http.conn.HttpClientConnectionManager",
   "org.apache.http.pool.ConnPoolControl",
   "software.amazon.awssdk.http.apache.internal.conn.Wrapped"],
  ["org.apache.http.conn.HttpClientConnectionManager",
   "software.amazon.awssdk.http.apache.internal.conn.Wrapped"]
]
Was this page helpful?
0 / 5 - 0 ratings