graalvm

Bruno do Nascimento Maciel 2024-11-07T08:23:02.921479Z

Hey folks, I am trying to run an web application using GraalVM. I am having problems while executing the native image.

Bruno do Nascimento Maciel 2024-11-07T08:25:27.181829Z

I am using this Dockerfile to generate the container image to execute the application:

Bruno do Nascimento Maciel 2024-11-07T08:27:11.595359Z

While building the image, we have this output:

Bruno do Nascimento Maciel 2024-11-07T08:29:18.055289Z

While executing the container I am getting this exception:

Exception in thread "main" java.lang.ExceptionInInitializerError
2024-11-07T08:28:30.145812583Z Caused by: java.io.FileNotFoundException: Could not locate rango/components__init.class, rango/components.clj or rango/components.cljc on classpath.
2024-11-07T08:28:30.145858083Z 	at clojure.lang.RT.load(RT.java:482)
2024-11-07T08:28:30.145860583Z 	at clojure.lang.RT.load(RT.java:444)
2024-11-07T08:28:30.145861833Z 	at clojure.core$load$fn__6931.invoke(core.clj:6189)
2024-11-07T08:28:30.145902750Z 	at clojure.core$load.invokeStatic(core.clj:6188)
2024-11-07T08:28:30.145904791Z 	at clojure.core$load.doInvoke(core.clj:6172)
2024-11-07T08:28:30.145905666Z 	at clojure.lang.RestFn.invoke(RestFn.java:411)
2024-11-07T08:28:30.145906750Z 	at clojure.lang.Var.invoke(Var.java:386)
2024-11-07T08:28:30.145907625Z 	at clojure.lang.Util.loadWithClass(Util.java:251)
2024-11-07T08:28:30.145908458Z 	at rango.components.<clinit>(Unknown Source)

Bruno do Nascimento Maciel 2024-11-07T08:30:28.606839Z

rango/components__init.class this is the namespace that holds my main function. I am trying to figure out why it is not being found, but without success.

Bruno do Nascimento Maciel 2024-11-07T08:31:39.496969Z

I have to initialize it at run time --initialize-at-run-time=rango.components

Bruno do Nascimento Maciel 2024-11-07T08:37:34.109629Z

If I remove the --initialize-at-run-time=rango.components I am still able to build the docker image but while executing it I am greeted with:

Error: Could not find or load main class rango.components
2024-11-07T08:37:08.575149635Z Caused by: java.lang.ClassNotFoundException: rango.components

Bruno do Nascimento Maciel 2024-11-07T08:43:28.622689Z

This is my project.clj file, just to provide more context.

borkdude 2024-11-07T10:19:57.128309Z

Check out https://github.com/clj-easy/graal-build-time and use it in your project

Bruno do Nascimento Maciel 2024-11-07T10:48:12.421459Z

I am trying to use graal-build-time . But I am only able to build the image with --initialize-at-run-time=rango

borkdude 2024-11-07T10:48:39.371789Z

why?

Bruno do Nascimento Maciel 2024-11-07T10:52:05.775569Z

I get this error:

Error: Classes that should be initialized at run time got initialized during image building:
#13 70.73  org.joda.time.DateTimeZone was unintentionally initialized at build time. To see why org.joda.time.DateTimeZone got initialized use --trace-class-initialization=org.joda.time.DateTimeZone
And all those are traced back to the main namespace of the project rango.components

borkdude 2024-11-07T10:56:38.033269Z

Then also add:

--initialize-at-build-time=org.joda.time.DateTimeZone

borkdude 2024-11-07T10:57:07.082009Z

if Java classes are used at the top level in Clojure namespaces, you also need to include those

Bruno do Nascimento Maciel 2024-11-07T11:34:02.931259Z

Adding all the reported classes worked for the image build.

Bruno do Nascimento Maciel 2024-11-07T11:34:12.854459Z

RUN native-image \
      --report-unsupported-elements-at-runtime \
      --initialize-at-build-time=io.opentracing.util.GlobalTracer \
      --initialize-at-build-time=org.slf4j.helpers.Reporter,org.slf4j.LoggerFactory \
      --initialize-at-build-time=org.pg.Pool \
      --initialize-at-build-time=io.opentelemetry.api.common.ArrayBackedAttributes,io.opentelemetry.api.DefaultOpenTelemetry,io.opentelemetry.api.GlobalOpenTelemetry,io.opentelemetry.context.propagation.DefaultContextPropagators \
      --initialize-at-build-time=org.eclipse.jetty.http.HttpVersion,org.eclipse.jetty.http2.hpack.HpackContext,org.eclipse.jetty.util.BufferUtil,org.eclipse.jetty.http.HttpMethod,org.eclipse.jetty.server.Response,org.eclipse.jetty.http.DateGenerator,org.eclipse.jetty.http.HttpHeader,org.eclipse.jetty.http2.hpack.HpackEncoder,org.eclipse.jetty.util.TypeUtil,org.eclipse.jetty.http.compression.Huffman,org.eclipse.jetty.util.StringUtil,org.eclipse.jetty.http.PreEncodedHttpField,org.eclipse.jetty.http.HttpScheme,org.eclipse.jetty.http.HttpTokens \
      --initialize-at-build-time=org.joda.time.chrono.ISOChronology,org.joda.time.Period,org.joda.time.DateTimeFieldType,org.joda.time.DateTimeZone,org.joda.time.chrono.ISOYearOfEraDateTimeField,org.joda.time.chrono.BasicGJChronology,org.joda.time.chrono.GregorianChronology \
      --initialize-at-build-time=org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers,org.bouncycastle.asn1.nist.NISTObjectIdentifiers,org.bouncycastle.util.Strings,org.bouncycastle.crypto.CryptoServicesRegistrar,org.bouncycastle.asn1.x9.X9ObjectIdentifiers,org.bouncycastle.util.Properties,org.bouncycastle.crypto.engines.Salsa20Engine,org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers,org.bouncycastle.asn1.x509.X509ObjectIdentifiers \
      --features=clj_easy.graal_build_time.InitClojureClasses \
      --enable-url-protocols=http,https \
      -Dio.pedestal.log.defaultMetricsRecorder=nil \
      -jar ./target/rango-0.1.0-SNAPSHOT-standalone.jar \
      -H:Name=rango-native \
      -H:+ReportExceptionStackTraces \
      -H:+StaticExecutableWithDynamicLibC \
      -H:CCompilerOption=-pipe

Bruno do Nascimento Maciel 2024-11-07T11:34:59.465169Z

But while I execute the image, I am still getting

Error: Could not find or load main class rango.components
2024-11-07T11:33:49.257499173Z Caused by: java.lang.ClassNotFoundException: rango.components
2024-11-07T11:33:50.289990937Z Error: Could not find or load main class rango.components
2024-11-07T11:33:50.290015563Z Caused by: java.lang.ClassNotFoundException: rango.components

Bruno do Nascimento Maciel 2024-11-07T11:40:32.583059Z

From the logs I see that rango is being loaded by clj-easy/graal-build-time

Bruno do Nascimento Maciel 2024-11-07T11:40:36.046759Z

[clj-easy/graal-build-time] Registering packages for build time initialization: clojure, buddy, camel_snake_kebab, cheshire, clj_commons, clj_easy.graal_build_time, clojurewerkz.scrypt, cognitect, common_clj.error, common_clj.integrant_components, , common_clj.traceability, crypto, humanize, integrant, io.pedestal, java_time, less.awful, medley, pg, porteiro_component, postgresql_component, rango, ring.middleware, ring.util, schema, taoensso, weavejester

borkdude 2024-11-07T11:52:17.679179Z

Is range.components dynamically loaded using requiring-resolve or so?

Bruno do Nascimento Maciel 2024-11-07T11:55:21.161189Z

no, it is the namespace that holds the -main function and project.clj points to it: :main rango.components

borkdude 2024-11-07T12:00:12.701669Z

Perhaps any of your other namespaces are still requiring rango.components dynamically or so? I would need to see the entire trace (perhaps better to put it in a gist)

Bruno do Nascimento Maciel 2024-11-07T12:02:58.481359Z

while executing the image this is the entire trace, there is no more information:

Error: Could not find or load main class rango.components
2024-11-07T11:33:49.257499173Z Caused by: java.lang.ClassNotFoundException: rango.components

Bruno do Nascimento Maciel 2024-11-07T12:03:42.308849Z

rango.components is not required anywhere in the source code

👍 1
borkdude 2024-11-07T12:03:49.046259Z

it looks like this is logged by timbre. perhaps you can tweak things to show the entire exception or so.

Bruno do Nascimento Maciel 2024-11-07T12:04:20.930689Z

I am going to try

borkdude 2024-11-07T12:04:43.155599Z

could it be that integrant is loading namespaces dynamically

🤔 1
borkdude 2024-11-07T12:07:05.430399Z

I'm not sure wether this is called or not, but could be

Bruno do Nascimento Maciel 2024-11-07T12:09:51.827279Z

is there a documented way to make the native image build process to recognize it properly?

Bruno do Nascimento Maciel 2024-11-07T12:10:38.631509Z

but I will try o see the complete trace first

borkdude 2024-11-07T12:15:09.484649Z

> make the native image build process to recognize it properly? not sure what you mean by this

Bruno do Nascimento Maciel 2024-11-07T12:19:08.903079Z

If there is a expected workaround when the code loads namespaces dynamically.

Bruno do Nascimento Maciel 2024-11-07T12:19:57.569479Z

I don't want to stop using integrant to make it work sadge

borkdude 2024-11-07T12:23:27.955039Z

I'm not sure if integrant is the cause, let's first dig into the exception

👍 1
Bruno do Nascimento Maciel 2024-11-07T19:38:11.783149Z

I was able to get this stack trace while building the native image: https://gist.github.com/macielti/1509469b760baf8143dc12310806c5d2

borkdude 2024-11-07T19:38:48.043409Z

interesting:

#15 130.5       at clojure.core$require.invokeStatic(core.clj:6066)
#15 130.5       at io.pedestal.interceptor.chain$loading__6812__auto____8196.invoke(chain.clj:14)
#15 130.5       at io.pedestal.interceptor.chain__init.load(Unknown Source)
#15 130.5       at io.pedestal.interceptor.chain__init.<clinit>(Unknown Source)

borkdude 2024-11-07T19:39:35.185899Z

ah:

#15 130.5       at common_clj.integrant_components.service$loading__6812__auto____6648.invoke(service.clj:1)
#15 130.5       at common_clj.integrant_components.service__init.load(Unknown Source)
#15 130.5       at common_clj.integrant_components.service__init.<clinit>(Unknown Source)

Bruno do Nascimento Maciel 2024-11-07T19:45:44.428789Z

this is the source code for the https://github.com/macielti/common-clj/blob/main/src/common_clj/integrant_components/service.clj

Bruno do Nascimento Maciel 2024-11-07T19:46:31.125149Z

but I think that this still related with how rango.components loads this, that makes sense?

Bruno do Nascimento Maciel 2024-11-07T19:49:21.230859Z

I saw that integrant is compatible with graalvm out of the box https://github.com/clj-easy/graalvm-clojure/tree/master/integrant

borkdude 2024-11-07T19:49:36.610149Z

perhaps something is trying to reload your code somehow at runtime in the native image?

borkdude 2024-11-07T19:49:47.017829Z

do you use any tools like clj-reload or tools.namespace.refresh?

Bruno do Nascimento Maciel 2024-11-07T19:51:07.884839Z

no, I am not intentionally using any of those

borkdude 2024-11-07T19:51:53.113559Z

also replacing this:

:aot :all
with:
:aot [rango.components]

borkdude 2024-11-07T19:52:10.147869Z

:aot :all is known to cause problems with native-image. this is probably not the issue here

borkdude 2024-11-07T19:52:18.889199Z

but still good to exclude this source of error

borkdude 2024-11-07T19:54:45.954529Z

can you also set this on the top level after your ns form:

(alter-var-root #'clojure.core/*loading-verbosely* (constantly true))

borkdude 2024-11-07T19:54:58.438319Z

this will print if any other namespaces get loaded beyond that

borkdude 2024-11-07T19:55:06.659519Z

e.g.:

user=> (alter-var-root #'clojure.core/*loading-verbosely* (constantly true))
true
user=> (require '[clojure.set])
(clojure.core/load "/clojure/set")

Bruno do Nascimento Maciel 2024-11-07T20:08:00.403699Z

nothing was printed

Bruno do Nascimento Maciel 2024-11-07T20:09:29.451439Z

wait, loading the namespace at REPL it printed a lot of stuff

Bruno do Nascimento Maciel 2024-11-07T20:14:40.796279Z

https://gist.github.com/macielti/72b375f2dcc2997b5bbca45f64f1e3b4

Bruno do Nascimento Maciel 2024-11-07T20:19:38.890229Z

Manually requiring the ns, nothing is printed:

(require '[rango.components])
=> nil

borkdude 2024-11-07T20:30:14.540339Z

that's not what i meant

borkdude 2024-11-07T20:30:24.356549Z

I mean, compile the native image and then see if it prints anything

👍 1
borkdude 2024-11-07T20:31:13.241699Z

you could also try this by starting your main function from the repl

Bruno do Nascimento Maciel 2024-11-07T20:33:58.690249Z

I have a question, it is better to use --no-fallback ? when it is added the image build fails with the exception that I shared previously. When I remove it, the build process is completed with success, but fails while executing the image.

borkdude 2024-11-07T20:34:53.488629Z

yes, use --no-fallback. a fallback image doesn't make sense, it's basically just a JVM

👍 1
Bruno do Nascimento Maciel 2024-11-07T20:40:36.960339Z

There was nothing printed related with clojure.core/load https://gist.github.com/macielti/5805a7ac778c7b518708fd407f7c5903

Bruno do Nascimento Maciel 2024-11-07T20:53:28.235629Z

I made the repository public: https://github.com/macielti/rango I think that this use case will help others with the same problem while trying to use GraalVM to run webapps

Bruno do Nascimento Maciel 2024-11-09T13:28:29.980779Z

It stopped working again, but i upgraded it to the latest version and it worked.

👀 1
Bruno do Nascimento Maciel 2024-11-09T13:30:44.951709Z

I am suspicious about the pedestal.interceptor I will start to add them to see what happpens.

Bruno do Nascimento Maciel 2024-11-09T20:03:29.879209Z

@borkdude could you review this PR? https://github.com/clj-easy/graalvm-clojure/pull/64 I updated Pedestal adding more use cases and bump the dependencies.

borkdude 2024-11-09T20:06:25.016889Z

Looks great. I don't know if the markdown table changes are intentional.

borkdude 2024-11-09T20:06:38.630739Z

and the native-image invocation contains a specific path of your system

Bruno do Nascimento Maciel 2024-11-09T20:10:03.466919Z

borkdude 2024-11-09T20:11:16.203719Z

lread 2024-11-09T21:48:33.287789Z

Awesome of you to share your findings @brunodonascimentomaci! Thanks so much!

Bruno do Nascimento Maciel 2024-11-09T21:52:21.232869Z

I will put a lot of effort on it. I like to run my applications on isolated docker containers, and I liked the way that GraalVM native images deal with memory resources handling if compared with the JVM.

Bruno do Nascimento Maciel 2024-11-09T21:53:02.475359Z

My objective is to have a fully functional CRUD backend.

lread 2024-11-09T21:57:10.988119Z

Yeah, very cool! If you have any numbers, I'd love to hear about them: How is server startup time after moving to native-image? And how does memory usage compare?

Bruno do Nascimento Maciel 2024-11-09T21:58:49.967449Z

Yep, I already have the JVM version. After finishing the GraalVM one, we will be able to run it and compare side by side.

Bruno do Nascimento Maciel 2024-11-09T22:27:48.355619Z

Another thing that I observed was that the GraallVM 23 runs more smoothly while generating the native image and with more stable results while executing the binary.

Bruno do Nascimento Maciel 2024-11-09T22:30:45.971159Z

Do you know how we can get access to the latest docker image with the version 23? https://github.com/graalvm/container/pkgs/container/native-image

borkdude 2024-11-09T22:37:37.559819Z

Best place to ask would probably be that repo (or GraalVM Slack community)

Bruno do Nascimento Maciel 2024-11-09T22:38:19.596359Z

I think that one is a good candidate:

docker pull container-registry.oracle.com/graalvm/native-image:23

borkdude 2024-11-09T22:40:15.622369Z

👍

Bruno do Nascimento Maciel 2024-11-09T23:46:09.917829Z

The last one for today https://github.com/clj-easy/graalvm-clojure/pull/65, now adding PG2 reviewplease

borkdude 2024-11-08T09:59:44.326679Z

borkdude@m1-3 /tmp/rango (main) $ rg native-image
I can't find the script you use to compile the app

borkdude 2024-11-08T10:01:18.988979Z

maybe you can add some explicit script whichs builds the native image, else it's still hard to reproduce what you're doing

Bruno do Nascimento Maciel 2024-11-08T18:52:19.261779Z

I ended up reverting everything related to the GraalVM. I will create a new repo focused on the GraalVM version adding everything incrementally and testing it until it breaks.

borkdude 2024-11-08T18:52:47.979849Z

👍

borkdude 2024-11-08T18:52:56.082129Z

good approach

Bruno do Nascimento Maciel 2024-11-09T01:44:20.839289Z

One of the problems was the pedestal version, downgrading to:

[io.pedestal/pedestal.service "0.5.10"]
[io.pedestal/pedestal.jetty "0.5.10"]
Solved the problem. I used the version from https://github.com/clj-easy/graalvm-clojure/tree/master/pedestal as a reference.

lread 2024-11-22T15:07:14.280019Z

Although... back when all this was new we were taking time to record libraries that worked out-of-the-box here: https://github.com/clj-easy/graal-docs/blob/master/doc/external-resources.adoc#libraries-compatible-with-graalvm

1
lread 2024-11-22T15:08:32.820579Z

But we haven't maintained that list in eons. So... dunno.

Bruno do Nascimento Maciel 2024-11-22T15:09:20.829439Z

I think that it would be great to have a easy way to check if the lib works out of the box before testing it.

borkdude 2024-11-22T15:09:21.738599Z

yeah, but I don't think it makes sense to keep doing that since we already have enough examples of how to make a generic graalvm native-image project

borkdude 2024-11-22T15:09:37.862709Z

well yeah, that does make sense

👍 1
Bruno do Nascimento Maciel 2024-11-10T18:02:29.210019Z

It is done! It still missing the integration with New Relic for logs, but I will work on that later. https://github.com/macielti/rango-graalvm

🎉 1
Bruno do Nascimento Maciel 2024-11-10T18:03:30.606859Z

The JVM and the GraalVM side by side, on idle memory consumption.

Bruno do Nascimento Maciel 2024-11-10T18:06:03.122759Z

I will study more about GraalVM. I think we can optimize it for even better results. I discovered about GraalVM last week.

borkdude 2024-11-10T19:14:34.123639Z

amazing :)

lread 2024-11-10T21:31:59.630379Z

Wowzie!

Bruno do Nascimento Maciel 2024-11-21T21:47:13.277989Z

@borkdude https://github.com/clj-easy/graalvm-clojure/pull/66 reviewplease

borkdude 2024-11-21T21:49:45.205209Z

I wonder, does it make sense to include examples that don't require any configuration, as in "works out of the box"? This increases maintenance overhead a bit for stuff people likely won't look at because they won't run into any problems in the first place? What do you think @lee?

Bruno do Nascimento Maciel 2024-11-21T21:51:51.546819Z

I agree with you, but this one does not requires any configuration before executing lein do clean, uberjar, native, run-native

borkdude 2024-11-21T21:52:20.046289Z

wasn't that my point -- doesn't require any configuration?

Bruno do Nascimento Maciel 2024-11-21T21:54:36.716019Z

now I understand the point. It makes sense.

Bruno do Nascimento Maciel 2024-11-21T21:57:00.078559Z

I wasn't able to make the latest version of org.xerial/sqlite-jdbc work. We can still left the PR open and focus on seeking the configuration needed for the latest version.

👍 1
lread 2024-11-21T21:59:22.560939Z

Yeah, I agree with you both. We don't need to document when nothing special is required.

2024-11-07T10:03:32.631719Z

Hi folks! What is the best approach to use Datomic (I need Datomic Cloud Client) from GraalVM? I checked conversations here and in #datomic channel, but it was somewhere in 2021. Nevertheless like I understood some people at least partly succeed in it. Is there any guide how to do it? Looks like there problems with dynaload 🤔 And if to expand this question: how to run datomic testing in native image, since datomic.local seems will not work at GraalVM at all?

Oliver Marks 2024-11-07T12:21:41.218469Z

I am trying to compile a project with graalvm, I am hitting issues with a specific library called duratom which results in this error

Fatal error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a started Thread in the image heap. Thread name: clojure-agent-send-off-pool-0. Threads running in the image generator are no longer running at image runtime. Prevent threads from starting during image generation, or a started thread from being included in the image.
I believe its from this code
(def auth-session-db
  (duratom :local-file
           :file-path "/tmp/user-session-store.dat"
           :init {}))
I am unsure on the incantation I need to add to the command line to make this, tried some of the suggestions from the error with no luck so things like
--initialize-at-build-time=duratom.core.Duratom \
But to no avail, anyone have any tips before I remove the library and try and find something else ? I have built a version removing this functionality and it seems to build just fine so its something about duratom from what I can tell that graal does not like.

souenzzo 2024-11-07T13:34:55.671159Z

Use delay (def *auth-session-db (delay (duratom ....)) And, for sure, update all usages from auth-session-db to @*auth-session-db

Oliver Marks 2024-11-07T13:38:47.029289Z

@souenzzo thanks I will look into that, duatom itself is an atom so does everything become @@`auth-session-db` I assume there is no reason you can't double deref 😛

souenzzo 2024-11-07T13:42:37.242229Z

It really looks really ugly It is probably possible to fix it at duratom, avoiding the creation of threads before usage

Oliver Marks 2024-11-07T13:44:11.679919Z

yeah that was my thought as well, also less obvious whats going on, in this case I probably only have to do it in a couple of places, I will give it a go good to know about using delay as well 🙂