This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-01-24
Channels
- # adventofcode (1)
- # announcements (22)
- # babashka (30)
- # beginners (69)
- # calva (53)
- # cider (17)
- # cljfx (1)
- # clojure (2)
- # clojure-australia (1)
- # clojure-europe (1)
- # clojurescript (36)
- # code-reviews (10)
- # conjure (3)
- # cursive (2)
- # datomic (4)
- # fulcro (13)
- # graalvm (261)
- # luminus (2)
- # malli (1)
- # nrepl (13)
- # off-topic (19)
- # rdf (3)
- # reveal (1)
- # ring (3)
- # sci (66)
- # shadow-cljs (14)
- # spacemacs (1)
- # specmonstah (1)
- # test-check (1)
- # vim (2)
- # xtdb (14)
Hey all, I' trying to compile clojure-lsp
(#lsp) with Graalvm, with the help of @borkdude we managed to make it work with sqlite ๐ ,but I'm still having other issues, this time related to log4j
A huge context here: https://clojurians.slack.com/archives/CPABC1H61/p1611087562030000 Hey @borkdude I think moving the discussion to here would be better for others with same problems
So, with https://github.com/clojure-lsp/clojure-lsp/pull/267/files it seems it has 2 issues right now at build-time, one regarding log4j and another about some clojure.lang.Agent class. both graalvm says that I should try use --initialize-at-run-time
, so after I added --initialize-at-run-time=org.apache.log4j.LogManager
, it indeed fixed the log4j error (but probably it'll happen at run-time when I got to work), but I have no idea how to fix the clojure.lang.Agent
error
Log: https://pastebin.com/cKXDiQPv
clojure-lsp uses log4j
to log to a custom file /tmp/clojure-lsp.out
, LMK If you think this can be replace with other lib that works with graalvm
started using log4j for a project 6 years ago, still regretting it but it's always waiting in the background to get the boot
my usecase is spilling out logs to file or stdout in json format so i can feed them to elastic .... and log4j is not helping me there in any way
originally we hoped for interop with other java libraries logging that we use beneath but that never materialized since we need structured logs and not some freetext mayhem
@U6MHHF36J @UKFSJSM38 Havenโt tried with GraalVM native image, but it doesnโt use any reflection or dynamic namespace loading https://cambium-clojure.github.io/ except discovering the current *ns*
, which clojure.tools.logging
also does.
A simpler alternative could be java.util.logging
that is built into Java. The clojure.tools.logging
wrapper loads classes at runtime https://github.com/clojure/tools.logging/blob/master/src/main/clojure/clojure/tools/logging/impl.clj#L52
Thereโs a page on logging in native image: https://www.graalvm.org/reference-manual/native-image/Logging/
Thanks for the help @U6MHHF36J @kumarshantanu @borkdude I think replacing the log with another lib it'd be faster indeed, we just need a lib that logs to a custom file and that's it. I'll take a look at timbre, it seems to nave no issues with graalvm Otherwise test https://cambium-clojure.github.io/
@borkdude I made a simple clojure sample using timbre to log to a file (using spit-appender
) ang got https://pastebin.com/g9wqxqbv when compiling with graalvm
Debugging timbre's code it seems to just use http://clojure.java.io to log https://github.com/ptaoussanis/timbre/blob/56d67dd274d7d11ab31624a70b4b5ae194c03acd/src/taoensso/timbre/appenders/core.cljc#L75-L105
@UKFSJSM38 > Error: Frame states being merged are incompatible: unbalanced monitors - locked objects do not match This seems to indicate you are again not using 1.10.2-rc3
this is the same problem that Clojure had. You should probably make a PR to timbre to use clojure's locking
and not use monitor-enter/exit manually
I think I should follow this example: https://clojuredocs.org/clojure.core/locking
see https://github.com/clojure/clojure/commit/f5403e9c666f3281fdb880cb2c21303c273eed2d#diff-313df99869bda118262a387b322021d2cd9bea959fad3890cb78c6c37b448299
this is a very technical low level change, to ensure byte code analyzers can verify that the monitor enter and exit match up.
your PR would replace monitor enter/exit to just use clojure's locking
and not the current try catch
@UKFSJSM38 timbre allows using your own appender, so for now you could copy this code, fix it and use your custom appender
Oh, good idea, I'm coding the fix for timbre right now, but I'll proceed with a custom adapter until the PR get merged
if that code is still reachable native-image might still complain, but you can patch with alter-var-root for the moment being
(fn self [data]
(let [{:keys [output_]} data]
(let [output (force output_)]
(locking (Object.) ; For thread safety, Ref. #251
(try
(with-open [^java.io.BufferedWriter w (jio/writer fname :append append?)]
(.write w ^String output)
(.newLine w))
(catch java.io.IOException e
(if (:spit-appender/retry? data)
(throw e) ; Unexpected error
(do
(jio/make-parents fname)
(self (assoc data :spit-appender/retry? true))))))))))
Does that makes sense?@borkdude would this code be enough to fix the issue right now?
https://pastebin.com/y1L3HEPU
WIth that I'm still having the same unbalanced monitors - locked objects do not match
This is the https://pastebin.com/gTB7QtB6.
Not sure the alter-var-root
is having effect
The thing with direct linking is, is that this default appender may be used in multiple places, so alter-var-rooting it only at the source spot won't suffice if that's the case
didn't work too, but I released a local version of my change on timbre and got other issue now, will paste the output
@borkdude I think this is the only missing issue: https://pastebin.com/JPv6vZ0s
About the clojure.lang.Agent
Hum, but we start that on main already: https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/main.clj#L427
Yep, that's what I'm trying to understanding why it's not working, we just call this function run
from the -main
https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/main.clj#L431
I'll keep commenting server declaration until the erros is gone, then we ll know what line is the offending one
weird, since we start the nrepl server here, but requiring it's enough to graal compile blow up: https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/main.clj#L414
could be one of these... https://github.com/nrepl/nrepl/blob/master/src/clojure/nrepl/server.clj#L5-L15
Just searched graalvm and found a issue where you are mentioned ๐ https://github.com/nrepl/nrepl/issues/181
It's for debugging AFAIK @borkdude, @U0BUV7XSA maybe can confirm that
@UKFSJSM38 what you can maybe do is copy paste the nrepl completions namespace into your own repo
moving to dev deps maybe is enough and the recommended IMO, I'll try to move to another profile so
Yes!
I just noticed that now it compiles but it seems that the app starts and then finish, while when running via lein run
it keeps the socket open waiting for the LSP IO
@borkdude it compiles clojure-lsp perfect, is just that it just exit the app, do I need to make anything more?
I pushed what I done so far here: https://github.com/clojure-lsp/clojure-lsp/pull/267
Oh, actually, it's throwing an exception at runtime and the log is going to the file, I didn't notice it ๐
2021-01-24T20:44:40.990Z gregnix-note INFO [clojure-lsp.main:415] - Starting server...
2021-01-24T20:44:41.000Z gregnix-note ERROR [taoensso.timbre:796] - Uncaught exception on thread: main
clojure_lsp.main.main
...
clojure-lsp.main/-main main.clj: 436
clojure-lsp.main/-main main.clj: 438
clojure-lsp.main/run main.clj: 418
org.eclipse.lsp4j.launch.LSPLauncher.createServerLauncher LSPLauncher.java: 46
org.eclipse.lsp4j.jsonrpc.Launcher$Builder.create Launcher.java: 321
org.eclipse.lsp4j.jsonrpc.Launcher$Builder.createProxy Launcher.java: 364
org.eclipse.lsp4j.jsonrpc.services.ServiceEndpoints.toServiceObject ServiceEndpoints.java: 41
...
com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass DynamicProxySupport.java: 113
com.oracle.svm.core.util.VMError.unsupportedFeature VMError.java: 86
com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.eclipse.lsp4j.services.LanguageClient, interface org.eclipse.lsp4j.jsonrpc.Endpoint] 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.
Ok, after adding H:DynamicProxyConfigurationResources
with those classes it seems to work ๐ , let me test it
I still could not make it work ๐ It compiles and start without any issues, but it seems to be some wrong classes from the same interface... It's using the https://github.com/eclipse/lsp4j/blob/master/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/GenericEndpoint.java#L38 instead of the https://github.com/eclipse/lsp4j/blob/master/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/RemoteEndpoint.java#L46, so when LSP client (lsp-mode) calls the server first methods (initialize), it returns this error:
Jan 24, 2021 9:28:05 PM org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint request
WARNING: Unsupported request method: initialize
This is something that seems relevant, the dynamic_proxy.json:
[
["org.eclipse.lsp4j.services.LanguageClient", "org.eclipse.lsp4j.jsonrpc.Endpoint"]
]
BTW https://github.com/eclipse/lsp4j/blob/master/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/ServiceEndpoints.java#L38-L55 that creates the proxy dynamically that I needed to setup this โ๏ธ
I already tried to change https://github.com/clojure-lsp/clojure-lsp/pull/267/files#diff-47adefe2f307a723b07cd0862a4478ec5d038fa6a8ba1733f92560c21e2f49bbR299 to reify
instead of proxy
also.
I feel like this is the last error or something like that ๐คI'm stuck with this last error โ๏ธ found this on lsp4j side: https://github.com/eclipse/lsp4j/issues/349 This is the only thing I get it on strerr:
Jan 26, 2021 8:06:04 PM org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint request
WARNING: Unsupported request method: initialize
While other logs not using graalvm say something like that:
Jan 26, 2021 7:02:07 PM org.eclipse.lsp4j.jsonrpc.RemoteEndpoint handleCancellation
WARNING: Unmatched cancel notification for request id 9710
what makes me guess that for some reason, it's using the wrong implementation of the lsp4j Endpoint interfaceI kind of found the issue, I made a simple reify of the LanguageServer
from lsp4j
:
(defn fake-server []
(reify LanguageServer
(^CompletableFuture initialize [_this ^InitializeParams params]
(do
(println "INITIALIZING!")
(log/info "INITIALIZING!")))
(getTextDocumentService [_this]
nil)
(getWorkspaceService [_this]
nil)))
During the start of the app, I call:
(log/info "-->" (class server)
(:members (r/reflect server)))
That prints all methods from the class, it works perfect with lein run
,but with graalvm it prints #{}
That is just a simple interface: https://github.com/eclipse/lsp4j/blob/master/org.eclipse.lsp4j/src/main/java/org/eclipse/lsp4j/services/LanguageServer.java#L28
@UKFSJSM38 what error do you get when using it?
Jan 27, 2021 9:46:32 AM org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint request
WARNING: Unsupported request method: initialize
this is what I get from lein run
/ java
from graalvm home:
--> clojure_lsp.main$fake_server$reify__31521 #{#clojure.reflect.Method{:name initialize, :return-type java.util.concurrent.CompletableFuture, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [org.eclipse.lsp4j.InitializeParams], :exception-types [], :flags #{:public}} #clojure.reflect.Method{:name getTextDocumentService, :return-type org.eclipse.lsp4j.services.TextDocumentService, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [], :exception-types [], :flags #{:public}} #clojure.reflect.Field{:name const__4, :type java.lang.Object, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Method{:name meta, :return-type clojure.lang.IPersistentMap, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [], :exception-types [], :flags #{:public}} #clojure.reflect.Field{:name const__2, :type clojure.lang.Var, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Method{:name getWorkspaceService, :return-type org.eclipse.lsp4j.services.WorkspaceService, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [], :exception-types [], :flags #{:public}} #clojure.reflect.Field{:name const__1, :type clojure.lang.Var, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Field{:name const__6, :type clojure.lang.Keyword, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Method{:name withMeta, :return-type clojure.lang.IObj, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [clojure.lang.IPersistentMap], :exception-types [], :flags #{:public}} #clojure.reflect.Constructor{:name clojure_lsp.main$fake_server$reify__31521, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [clojure.lang.IPersistentMap], :exception-types [], :flags #{:public}} #clojure.reflect.Constructor{:name clojure_lsp.main$fake_server$reify__31521, :declaring-class clojure_lsp.main$fake_server$reify__31521, :parameter-types [], :exception-types [], :flags #{:public}} #clojure.reflect.Field{:name const__3, :type clojure.lang.Keyword, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Field{:name const__0, :type clojure.lang.Var, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Field{:name const__5, :type clojure.lang.Keyword, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}} #clojure.reflect.Field{:name __meta, :type clojure.lang.IPersistentMap, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:final}} #clojure.reflect.Field{:name const__7, :type java.lang.Object, :declaring-class clojure_lsp.main$fake_server$reify__31521, :flags #{:public :static :final}}}
It's printing correct all methods from that reify@UKFSJSM38 Maybe this will work: Is it possible to make the reified object at the top-level, at compile time?
Same issue, this is the output of that print from above:
--> class clojure_lsp.main$reify__31521 #{}
yep, maybe try with gen-class? https://stackoverflow.com/a/8614783
@UKFSJSM38 Maybe try once without type-hinting the initialize
method? Since itโs the only method in the interface, it should work without hinting due to auto-inference.
But how can I get the class name? or you mean add the interface to the reflection config?
good point @kumarshantanu, I'll need type hint for other methods later, but for that test it's valid, let me try
It gives another error, but it seems is a step ahead ๐
SEVERE: Unable to invoke no-args constructor for class org.eclipse.lsp4j.adapters.InitializeParamsTypeAdapter$Factory. Registering an InstanceCreator with Gson for this type may fix this problem.
java.lang.RuntimeException: Unable to invoke no-args constructor for class org.eclipse.lsp4j.adapters.InitializeParamsTypeAdapter$Factory. Registering an InstanceCreator with Gson for this type may fix this problem.
at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
at com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter(JsonAdapterAnnotationTypeAdapterFactory.java:55)
at com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.create(JsonAdapterAnnotationTypeAdapterFactory.java:49)
at com.google.gson.Gson.getAdapter(Gson.java:423)
at com.google.gson.Gson.fromJson(Gson.java:887)
at org.eclipse.lsp4j.jsonrpc.json.adapters.MessageTypeAdapter.fromJson(MessageTypeAdapter.java:329)
at org.eclipse.lsp4j.jsonrpc.json.adapters.MessageTypeAdapter.parseParams(MessageTypeAdapter.java:249)
at org.eclipse.lsp4j.jsonrpc.json.adapters.MessageTypeAdapter.read(MessageTypeAdapter.java:119)
at org.eclipse.lsp4j.jsonrpc.json.adapters.MessageTypeAdapter.read(MessageTypeAdapter.java:55)
at com.google.gson.Gson.fromJson(Gson.java:888)
at org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler.parseMessage(MessageJsonHandler.java:119)
at org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler.parseMessage(MessageJsonHandler.java:114)
at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.handleMessage(StreamMessageProducer.java:193)
at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.listen(StreamMessageProducer.java:94)
at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor.run(ConcurrentMessageProcessor.java:113)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.lang.Thread.run(Thread.java:834)
at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:519)
at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Method.java:566)
at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:50)
at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223)
... 21 more
Caused by: java.lang.IllegalArgumentException: Class org.eclipse.lsp4j.adapters.InitializeParamsTypeAdapter$Factory is instantiated reflectively but was never registered. Register the class by using org.graalvm.nativeimage.hosted.RuntimeReflection
at com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.hubErrorStub(SubstrateAllocationSnippets.java:247)
at sun.misc.Unsafe.allocateInstance(Unsafe.java:840)
... 24 more
Process clojure-lsp stderr<1> finished
Oh this looks a good start:
Class org.eclipse.lsp4j.adapters.InitializeParamsTypeAdapter$Factory is instantiated reflectively but was never registered. Register the class by using
If that works, probably I'll need to add https://github.com/eclipse/lsp4j/tree/master/org.eclipse.lsp4j/src/main/xtend-gen/org/eclipse/lsp4j/adapters to the reflection too, if that works, I'll certainly open an issue on lsp4j with steps how to make it work with graalvm ๐ค
Good news, problem solved with the suggestion from you, @borkdude , I added some classes to reflection json and it's getting into the method initialize correctly now โ The next issue ๐ is: clojure-lsp tries to parse Gson json objects via https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/interop.clj#L416-L432, and it's returning this error on runtime with graal:
com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine PosixJavaThreads.java: 192
com.oracle.svm.core.thread.JavaThreads.threadStartRoutine JavaThreads.java: 519
java.lang.Thread.run Thread.java: 834
java.util.concurrent.ThreadPoolExecutor$Worker.run ThreadPoolExecutor.java: 628
java.util.concurrent.ThreadPoolExecutor.runWorker ThreadPoolExecutor.java: 1128
java.util.concurrent.FutureTask.run FutureTask.java: 264
java.util.concurrent.Executors$RunnableAdapter.call Executors.java: 515
org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor.run ConcurrentMessageProcessor.java: 113
org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.listen StreamMessageProducer.java: 94
org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.handleMessage StreamMessageProducer.java: 194
org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.consume RemoteEndpoint.java: 190
org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.handleRequest RemoteEndpoint.java: 261
org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.request GenericEndpoint.java: 120
org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.lambda$null$0 GenericEndpoint.java: 65
...
clojure_lsp.main.proxy$clojure_lsp.ClojureExtensions$LanguageServer$c8d5825a.initialize
clojure-lsp.main/fn/fn main.clj: 326
clojure-lsp.main/client-settings main.clj: 278
clojure-lsp.interop/json->clj interop.clj: 421
...
java.lang.IllegalArgumentException: No matching field found: isJsonNull for class com.google.gson.JsonObject
After that I get:
java.lang.NoSuchMethodError: java.lang.reflect.AccessibleObject.canAccess(java.lang.Object)
Not sure what to add to reflection.json nowyou can also run with a graalvm agent and then it will spit out all the things it needed reflection for during the run of a program
it will also spit out a lot of classes you don't need, but it might give you some idea
Yeah, I thought about that, but the issue is that is not trivial to test clojure-lsp on terminal, we need to send LSP spec jsons, I'll try to move it to a script that use the java from graalvm, hope it works
Having this now:
Can't call public method of non-public class: public sun.nio.fs.UnixPath sun.nio.fs.UnixPath.getParent()
even with this on reflection.json:
{
"name": "sun.nio.fs.UnixPath",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
Related to this line: https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/crawler.clj#L199@UKFSJSM38 Try hinting dir
as a public interface? (.getParent ^java.nio.file.Path dir)
Hum, good guess again, let me try it, I was thinking in refactor that to use
instead of manually using java classes, similar how clj-kondo do to get the config dir
@UKFSJSM38 You should never have to include OS specific things in your reflection config, this config won't work on Windows for example
I think you need a type hint here: https://github.com/clojure-lsp/clojure-lsp/blob/0b881960cbce9cb09a6bbb8738a94f390b205d94/src/clojure_lsp/crawler.clj#L196
You are right, something like that?
[dir ^java.nio.file.Path (shared/uri->path project-root)]
@borkdudeyes, but if you place this type hint on your function it would be even better
Ok, so the type hinting fixed that case, but It being really hard to make lsp4j work correctly, it looks it uses a lot of reflection and I'm not sure I'm on the right path... The https://github.com/clojure-lsp/clojure-lsp/pull/267/files#diff-736ec9c37d3ba754db0a1f0c6cc5df40e8f284fb11e4824e55920cce814c37d8R1 already looks huge and I started to think that adding some classes have caused inconsistency like request params objects come with some fields null and etc. Also I could not run the java from graalvm to check what should looks like my reflection.json since is not available for my NixOS ๐
@UKFSJSM38 If you add this to your project.clj
, you might get all the reflection warnings with lein
before even trying GraalVM:
:global-vars {*warn-on-reflection* true
*assert* true
*unchecked-math* :warn-on-boxed}
Yep, that's the agent that should be used with graal's java that is not available for NixOs :/
you can just download graalvm to a Downloads directory. there is no need to "install". Or aren't you allowed to downloading anything in nixOS?
yes, it's standalone. just unzip it and set GRAALVM_HOME
to the thing and then use $GRAALVM_HOME/native-image
thanks!
but to run with graal's java it should be something like $GRAALVM_HOME/bin/java
I guess
that's right. you can export GRAALVM_HOME on the path like export PATH="$GRAALVM_HOME/bin:$PATH"
I tried the docker before, but failed for some reason, I gave a try again and it worked run the java from graal with the agent on NixOS via docker:tada:
I got a huge relfect json config generated with a lot of classes from clojure and some with dynamic names (using the object reference Id)
Yeah, there is a lot of stuff in there that you don't need. I'm not sure why it's showing up. Maybe it helps if you run with direct linking.
Ok, use the direct-linking helped reducing some weird classes, I used the output config to the native image input and the result looks better than before ๐
now it seems to load LSP correctly and communication work (json ser/deser), but I'm having a issue when responding with clojure.spec.alpha/conform
https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/interop.clj#L409.
Basically we use this function everytime we want to respond to client, so we validate if server response is following the spec and do some clj-map->java transformations (nothing magical, just instantiating the class manually)
https://pastebin.pl/view/6450ceea
It was related with some instances not being recognized in runtime, fixed it using some type hints ๐
Finally, after a lot of tries, the basic of clojure-lsp it's working compiled with GraalVM in https://github.com/clojure-lsp/clojure-lsp/pull/267, still need to test all the features to check there is no missing reflection/runtime errors, I'll probably test all features using graal's java with native agent again to make sure all reflection is caught in runtime, run Graal's java with the agent exporting all necessary analyzed files was a really game changer to make it work ๐ If everything runs well with the tests I'll setup the CI and test on macos/windows too, a lot of tests/work yet though. Huge thanks to @borkdude and @kumarshantanu for the help during this process, your help really kept me trying to make it work โค๏ธ
Hey @borkdude Just got a corner case with sqlite ๐
It seems to throw a segfault when the db does not exists: (missing .lsp/sqlite.db
)
https://pastebin.com/JqHJPJzk
Right on this line: https://github.com/clojure-lsp/clojure-lsp/blob/master/src/clojure_lsp/db.clj#L35
@UKFSJSM38 I don't know what causes this. Where did you get the suggestions for the reflection / JNI config for sqlite?
From the example: https://github.com/xerial/sqlite-jdbc/issues/413#issuecomment-525926912
@UKFSJSM38 Does his Java example work correctly when the db does not exist?
And to double check, you added all these classes to the JNI config right? https://github.com/mageddo/graalvm-examples/blob/59f1f1bf09894681edfddaa100b4504770ad0685/sqlite/src/main/java/com/mageddo/sqlite/JNIReflectionClasses.java#L46-L55 and to reflection
Yes, they are on https://github.com/ericdallo/sqlite-jni-graal-fix workaround
Oh, just found some classes missing from the fix, I don't know why, I'll add them too
That is done here, reflection: https://github.com/ericdallo/sqlite-jni-graal-fix/blob/61aba255490c5facbc94cabd43e22aff6a69e5a3/src-java/ericdallo/JNIReflectionClasses.java#L80
Btw, Quarkus is a Java framework by Oracle of which all extensions can be compiled to native. Sometimes nice to borrow stuff from as well: https://github.com/quarkusio/quarkus/tree/master/extensions/jdbc
Yep, it fixed the issue ๐ Probably it was the missing Throwable... that is not on that repo master, only on that specific commit
this is why you should probably not just switch to native, but offer this as something experimental ;)
but it's cool that you got sqlite to work. maybe I can switch my pod from go to clojure now ;) although the golang pod is really small (few megabytes)
does LSP native not require you to have sqlite installed, some libsqlite thing on your system? maybe try the binary in a bare ubuntu docker image (if that hasn't got it by default)?
Note that if you exclusively offer the native, you will probably have to provide it for the three major OSes and also a static one for alpine
yes, since I use NixOS there is no way to use a lib that I didn't tell it to use, but a test on a docker it'd really help to make sure
Yes, I have in mind setup a CI for the 3 OS building via GH actions, just like babashka
@borkdude how the nrepl just for dev would work? In the code should I check somehow what is the profile/some env var? This would affect graal?
I can add the dep for the dev profile only, but my question is how should I code that to use for dev only or something like that
yeah, you can do it through an env var or just by trying to resolve it on the top level:
(when-let [server-var (try (requiring-resolve 'nrepl.server/server) (catch Exception e nil))]
...
@UKFSJSM38 I also have this library: https://github.com/borkdude/dynaload
Oh I liked this one: https://github.com/babashka/babashka/blob/6dc6cbab12910f6b93fa52c66e2f7da1a3213b09/src/babashka/impl/classes.clj#L322
should I wrap the dynaload call with a try catch to check if nrepl is enabled? Or is better to check a env var?
What you can do is do a require based on an env var. And then use dynaload to use the var(s)
what is the point to use dynaload if I can just require 'mylist
? I think I don't get it
this is more useful for libraries than apps probably. in libraries you can say: I can work with these vars, if you have required the lib already
To keep it simple: Just do a try catch where you try to require/resolve the lib/vars. If you then provide the library on your classpath, the vars will work. Else they won't do anything.
ok, nrepl fixed using dynaload ๐
I tried to use SLF4j, log4j, log4j2 and logback, but all have issues with file appenders with graalvm... so I think timbre will be the only way
I fixed all found issues with reflection, need to test all features still.
I noticed that the first clojure-lsp classpath scan time using clj-kondo was increased, for a proect with 250 files to scan, while with JVM it takes 90s with graalvm takes 130s, not sure there is anything to improve that though.
About the image size, I removed a lot of unnecessary reflection, still the image size is about 110MB while the jar has 30MB, I found https://github.com/ptaoussanis/timbre/issues/326 regarding timbre that adds 50MB to the image size but not sure is happening
What you suggest to decrease the image size @borkdude?
@UKFSJSM38
> while with JVM it takes 90s with graalvm takes 130s, not sure there is anything to improve that though.
This is why I think the JVM makes most sense for projects like this, I keep recommending this for my own LSP server for clj-kondo
As for the image size, you can look at the analysis callgraph or use the dashboard options.
I see ๐ Besides that, everything is pretty much fast, like diagnostics, code actions, refacotings which improve user xp IMO I still need to make the memory test, then we can think in the tradeoff.
and there is also a dashboard option: https://www.graalvm.org/docs/tools/dashboard/?ojr=help%3Btopic%3Dgetting-started.md
I haven't used the dashboard that much, but it seems useful. You can drill down into classes
@borkdude for some reason the dashboard flag just gives me OoO even with a -JXmx of 6GB :man-shrugging:
I did some hacking to run Graalvm 21.0.0 on My NixOS (docker doesn't work) instead of 20.2.0 and the startup time fall from 130s -> 75s... it's faster than JVM classpath scan ๐ฎ (~87-90s), I will make more tests, special the memomry one (not sure how to measure that yet)
Also, I added the new -H:+InlineBeforeAnalysis
flag, not sure this could cause that performance improvement though
@UKFSJSM38 Be sure to use the --no-fallback
option so you won't get a fallback image, this is effectively just a JVM
You could also post an issue about a performance regression on the graalvm issue tracker
@borkdude sorry, I probably was not clear, I was using Graalvm 20.2 with all my tests because it's the only available for NixOS while this https://github.com/NixOS/nixpkgs/pull/105815 , also the docker image doesn't work with NixOS for some reason related with shared libs, but I managed to manually install the graalvm 21.0.0 in the nix way
yep, at least for the only thing I noticed that it was slower than JVM, now it's not anymore ๐
Using the dashboard, I can't see anything that is ocupying a huge space... it's ood that summing all those, it won't be equal to 110MB
Finally, after a lot of tries, the basic of clojure-lsp it's working compiled with GraalVM in https://github.com/clojure-lsp/clojure-lsp/pull/267, still need to test all the features to check there is no missing reflection/runtime errors, I'll probably test all features using graal's java with native agent again to make sure all reflection is caught in runtime, run Graal's java with the agent exporting all necessary analyzed files was a really game changer to make it work ๐ If everything runs well with the tests I'll setup the CI and test on macos/windows too, a lot of tests/work yet though. Huge thanks to @borkdude and @kumarshantanu for the help during this process, your help really kept me trying to make it work โค๏ธ