Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for use of java-server-sdk library in GraalVM native image applications #29

Open
ketronkowski opened this issue Dec 12, 2023 · 8 comments
Labels

Comments

@ketronkowski
Copy link

Is your feature request related to a problem? Please describe.

I currently use the launchdarkly JAVA SDK in Spring (Boot) based application. The library is currently not supported when compiling our application as a native image with GraalVM.

Describe the solution you'd like

I would like for the library to ship its JAR file with some JSON metadata in the META-INF/native-image/... directory, which enables support of dynamic features in the native image.

Describe alternatives you've considered

If it is not acceptable to include the metadata with the library, Oracle has created the graalvm-reachability-repository, which contains this metadata outside of the libraries JAR file. In an ideal world, all of the metadata is moved into the JARs of the libraries, but until our world has reached its ideal state, this repository can will be used.

the advantage of including this metadata with the library is that if your code changes, the metadata can change along with it. Otherwise, users would be broken until the graalvm-reachability-metadata is updated.

Additional context

I have tested the process of generating this metadata for the java-server-sdk library locally following instructions found here and was able to successfully build and run my application natively.

Some of the dynamic features used in the java-server-sdk need some additional configuration to allow it to be used. Please take a look at the following from GraalVM for more info on the dynamic areas

@tanderson-ld
Copy link
Contributor

Thank you @ketronkowski for making this request and letting us know what you're looking for and with ways to achieve support. I'll discuss this with my team and get back to you.

@tanderson-ld tanderson-ld added the enhancement New feature or request label Dec 13, 2023
@tanderson-ld
Copy link
Contributor

tanderson-ld commented Dec 14, 2023

@ketronkowski we discussed this on team and we don't have any objections to adding this meta info to the project.

Do you know which portions are not reachable?

We do have other priorities at the moment, so practically the best options to get this change integrated is if you submit a PR or provide patch code that we can work off of.

@ketronkowski
Copy link
Author

ketronkowski commented Dec 14, 2023

I boiled down all the output you get by generating the native image metadata from your test suite to just the reflect-config.json file with the following classes.

reflect-config.json ``` [ { "name":"com.launchdarkly.sdk.ContextKindTypeAdapter", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.EvaluationReasonTypeAdapter", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.LDValueTypeAdapter", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.json.SdkSerializationExtensions", "methods":[{"name":"getDeserializableClasses","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.BigSegmentStoreWrapper", "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, { "name":"com.launchdarkly.sdk.server.DataModel$FeatureFlag", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$FeatureFlag$Migration", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$Prerequisite", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$Rule", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$Segment", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$SegmentTarget", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$Target", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$VariationOrRollout", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModel$WeightedVariation", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModelSerialization$ClauseTypeAdapter", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModelSerialization$RolloutTypeAdapter", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.DataModelSerialization$SegmentRuleTypeAdapter", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.EventBroadcasterImpl", "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, { "name":"com.launchdarkly.sdk.server.FeatureFlagsState$FlagMetadata", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.FeatureFlagsState$JsonSerialization", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory", "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.MigrationExecutionFixture", "allDeclaredFields":true, "queryAllDeclaredMethods":true }, { "name":"com.launchdarkly.sdk.server.integrations.FileDataSourceParsing$FlagFileRep", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] }, { "name":"com.launchdarkly.sdk.server.subsystems.DataStoreTypes$ItemDescriptor", "allDeclaredFields":true, "methods":[{"name":"","parameterTypes":[] }] } ] ```

The most involved part for me (and possibly for your team) in making a PR to your repo is making changes to your grade build to generate this data as part of your build process with an up-to-date graalvm toolchain while keeping the backward compatibility that you need to generate the library for.

Possibly the most straightforward, short-term option is for me to make a PR to the graalvm-reachability-repository repository. I would be happy to contribute there as long as I have your support to do that on behalf of your team. I would also be happy to provide your team a patch for that repo directly and have your team make the PR.

@ketronkowski
Copy link
Author

I didn't answer your question in my above response.

The initial error we get is when initializing the LDClient(sdkKey). Looks like the problem stems from not being able to instantiate com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory refelctively. There may be follow on reflection errors. I did not try to surface every single one of them. The entire stacktrace is provided below.

com.launchdarkly.sdk.server.StreamProcessor$StreamInputException: com.launchdarkly.sdk.server.subsystems.SerializationException: com.launchdarkly.sdk.server.subsystems.SerializationException: com.launchdarkly.sdk.server.subsystems.SerializationException: java.lang.RuntimeException: Unable to invoke no-args constructor for class com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory. Registering an InstanceCreator with Gson for this type may fix this problem.
	at com.launchdarkly.sdk.server.StreamProcessor.parseStreamJson(StreamProcessor.java:412) ~[na:na]
	at com.launchdarkly.sdk.server.StreamProcessor.handlePut(StreamProcessor.java:338) ~[na:na]
	at com.launchdarkly.sdk.server.StreamProcessor.handleMessage(StreamProcessor.java:272) ~[na:na]
	at com.launchdarkly.sdk.server.StreamProcessor.handleEvent(StreamProcessor.java:261) ~[na:na]
	at com.launchdarkly.sdk.server.StreamProcessor.lambda$start$1(StreamProcessor.java:200) ~[na:na]
	at [email protected]/java.lang.Thread.runWith(Thread.java:1596) ~[sd-backend-template:na]
	at [email protected]/java.lang.Thread.run(Thread.java:1583) ~[sd-backend-template:na]
	at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:832) ~[sd-backend-template:na]
	at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:211) ~[na:na]
Caused by: com.launchdarkly.sdk.server.subsystems.SerializationException: com.launchdarkly.sdk.server.subsystems.SerializationException: com.launchdarkly.sdk.server.subsystems.SerializationException: java.lang.RuntimeException: Unable to invoke no-args constructor for class com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory. Registering an InstanceCreator with Gson for this type may fix this problem.
	at com.launchdarkly.sdk.server.StreamProcessorEvents.parsePutData(StreamProcessorEvents.java:149) ~[na:na]
	at com.launchdarkly.sdk.server.StreamProcessor.parseStreamJson(StreamProcessor.java:407) ~[na:na]
	... 8 common frames omitted
Caused by: com.launchdarkly.sdk.server.subsystems.SerializationException: com.launchdarkly.sdk.server.subsystems.SerializationException: java.lang.RuntimeException: Unable to invoke no-args constructor for class com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory. Registering an InstanceCreator with Gson for this type may fix this problem.
	at com.launchdarkly.sdk.server.DataModelSerialization.parseFullDataSet(DataModelSerialization.java:152) ~[na:na]
	at com.launchdarkly.sdk.server.StreamProcessorEvents.parsePutData(StreamProcessorEvents.java:133) ~[na:na]
	... 9 common frames omitted
Caused by: com.launchdarkly.sdk.server.subsystems.SerializationException: java.lang.RuntimeException: Unable to invoke no-args constructor for class com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory. Registering an InstanceCreator with Gson for this type may fix this problem.
	at com.launchdarkly.sdk.server.JsonHelpers.deserialize(JsonHelpers.java:73) ~[na:na]
	at com.launchdarkly.sdk.server.DataModelSerialization.parseFullDataSet(DataModelSerialization.java:136) ~[na:na]
	... 10 common frames omitted
Caused by: java.lang.RuntimeException: Unable to invoke no-args constructor for class com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory. Registering an InstanceCreator with Gson for this type may fix this problem.
	at com.launchdarkly.shaded.com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:228) ~[na:na]
	at com.launchdarkly.shaded.com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter(JsonAdapterAnnotationTypeAdapterFactory.java:55) ~[na:na]
	at com.launchdarkly.shaded.com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.create(JsonAdapterAnnotationTypeAdapterFactory.java:49) ~[na:na]
	at com.launchdarkly.shaded.com.google.gson.Gson.getAdapter(Gson.java:489) ~[sd-backend-template:7.1.1]
	at com.launchdarkly.shaded.com.google.gson.Gson.fromJson(Gson.java:962) ~[sd-backend-template:7.1.1]
	at com.launchdarkly.sdk.server.JsonHelpers.deserialize(JsonHelpers.java:71) ~[na:na]
	... 11 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
	at [email protected]/java.lang.reflect.Method.invoke(Method.java:580) ~[sd-backend-template:na]
	at com.launchdarkly.shaded.com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:50) ~[na:na]
	at com.launchdarkly.shaded.com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:225) ~[na:na]
	... 16 common frames omitted
Caused by: java.lang.IllegalArgumentException: Type com.launchdarkly.sdk.server.JsonHelpers$PostProcessingDeserializableTypeAdapterFactory is instantiated reflectively but was never registered. Register the type by adding "unsafeAllocated" for the type in reflect-config.json.
	at org.graalvm.nativeimage.builder/com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.instanceHubErrorStub(SubstrateAllocationSnippets.java:315) ~[na:na]
	at [email protected]/sun.misc.Unsafe.allocateInstance(Unsafe.java:884) ~[sd-backend-template:na]
	... 19 common frames omitted

@tanderson-ld
Copy link
Contributor

Hello again @ketronkowski. I am working through deeper investigation of this issue. I have reproduced the issue locally and am able to remedy it by providing various -config.json files in the META-INF/native-image folder of the project. The -config.json files I am using I got by using the graal tracing agent with the following argument.

-agentlib:native-image-agent=config-output-dir=META-INF/native-image

Is that what you used with the test suite that you eluded to?

I am investigating further if we can integrate that into our contract testing system as that exercises more unique code paths I believe. I also have to discuss the long term stability concerns of this approach with the team.

@tanderson-ld
Copy link
Contributor

@ketronkowski , I was able to use our contract testing infrastructure to generate the *-config.json files, however inside those files are many classes that LaunchDarkly is not responsible for including third party libraries that we import as well as some that we don't explicitly import. For example: "name":"apple.security.AppleProvider", . I expect this Apple one appears because I was running this on a Mac. It seems we start to get into the problem of which native platform dependencies are needed where and calling those out, which is ultimately what the JVM was intended to solve.

I discussed with a few members of the team regarding the technique of collecting this information, doing so in an automated way, as well as the potential fragilities of this mechanism. We believe it is likely better at this stage of support and community demand to leave it to you and the community to implement it in the graalvm-reachability-repository repository. One caution is that we may restructure our internal classes at some point in the future. We don't have any plans at the moment to do so, but thought it worth calling out that there may be a point in the future when the metadata needs some revision.

Please let us know your thoughts and if you think we have any incorrect understandings around the problem scope or details.

@ketronkowski
Copy link
Author

I am fine with that approach with the reachability-repository at this point and would be happy to start the PR process.

I assume that if there are any questions (about why I am making the PR request) I can point the maintainers back to this issue so they can see the background of the issue and why I am the one posting the PR.

@tanderson-ld
Copy link
Contributor

Please do! Let us know when you open the PR and we can help review as well. Thank you for helping resolve this.

@tanderson-ld tanderson-ld transferred this issue from launchdarkly/java-server-sdk May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants