Fork me on GitHub
#graalvm
<
2021-01-24
>
ericdallo04:01:33

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

ericdallo04:01:12

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

ericdallo04:01:36

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

kulminaator07:01:17

started using log4j for a project 6 years ago, still regretting it but it's always waiting in the background to get the boot

kulminaator07:01:48

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

kulminaator08:01:56

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

Shantanu Kumar11:01:32

@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.

Shantanu Kumar11:01:22

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

borkdude11:01:15

or don't use java logging at all and use timbre for example?

ericdallo14:01:46

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/

ericdallo14:01:14

@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

borkdude15:01:35

@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

borkdude15:01:20

or hmm timbre is using monitor-enter and monitor-exit by itself? hmmmm

ericdallo15:01:19

yes, they are using it!

borkdude15:01:19

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

ericdallo15:01:32

i'm using latest clojure 1.10.2-rc3

ericdallo15:01:10

I don't know what should I change, could you provide some materia/links?

borkdude15:01:42

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

ericdallo15:01:50

I see, I'll give a try, thanks @borkdude!

borkdude15:01:05

you can refer to CLJ-1472 for details

๐Ÿ‘ 3
borkdude15:01:06

@UKFSJSM38 timbre allows using your own appender, so for now you could copy this code, fix it and use your custom appender

borkdude15:01:16

an upstream PR is of course nicer long term

ericdallo15:01:49

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

borkdude15:01:42

if that code is still reachable native-image might still complain, but you can patch with alter-var-root for the moment being

๐Ÿ‘ 3
ericdallo15:01:59

(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?

ericdallo15:01:24

I removed the monitor usages replaced by the locking defmacro

borkdude15:01:10

should work

ericdallo16:01:08

@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

ericdallo16:01:38

This is the https://pastebin.com/gTB7QtB6. Not sure the alter-var-root is having effect

borkdude17:01:47

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

borkdude17:01:31

what about using a custom appender?

ericdallo17:01:53

didn't work too, but I released a local version of my change on timbre and got other issue now, will paste the output

ericdallo17:01:16

Oh, actually it worked after adding --allow-incomplete-classpath

ericdallo17:01:28

noice, another issue solved ๐Ÿ˜„

ericdallo17:01:00

@borkdude I think this is the only missing issue: https://pastebin.com/JPv6vZ0s About the clojure.lang.Agent

ericdallo17:01:30

It seems related to the threads that clojure-lsp spawns during the initialize

borkdude18:01:29

yeah, you should not do that on the top level

borkdude18:01:32

but in the -main

borkdude18:01:53

you don't launch any agents at the top level?

borkdude18:01:45

can you pinpoint who is starting this thread?

ericdallo18:01:26

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

borkdude19:01:18

@UKFSJSM38 if you avoid the run call, do you still get the error?

loading 3
ericdallo19:01:50

does not work even commenting the run @borkdude ๐Ÿ˜ฎ

ericdallo19:01:47

I'll keep commenting server declaration until the erros is gone, then we ll know what line is the offending one

borkdude19:01:15

maybe some other lib is spawning a future at the top level

borkdude19:01:30

try eliminating some libs from the ns decl

borkdude19:01:51

I've recently encountered this with some lib, don't remember which one it was

ericdallo19:01:01

Yeah, probably some lsp4j class

borkdude19:01:03

you can try --initialize-at-runtime=<the name of the class>

ericdallo19:01:03

Ok, found the offender require:

[nrepl.server :as nrepl.server]

ericdallo19:01:49

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

borkdude19:01:43

which library is this?

ericdallo19:01:58

nrepl/nrepl {:mvn/version "0.8.3"}

borkdude19:01:30

why do you have an nREPL server in LSP?

ericdallo19:01:42

Just searched graalvm and found a issue where you are mentioned ๐Ÿ˜‚ https://github.com/nrepl/nrepl/issues/181

ericdallo19:01:10

It's for debugging AFAIK @borkdude, @U0BUV7XSA maybe can confirm that

borkdude19:01:05

it sounds like this could be moved to a dev dependency

borkdude19:01:37

btw, I have an nREPL server in babashka which works with graalvm :P

borkdude19:01:41

built from the ground up

ericdallo19:01:06

> it sounds like this could be moved to a dev dependency I think so

borkdude19:01:38

oh yes, now I remember!

ericdallo19:01:41

Does babashka just require the same nrepl.server? how does that work hahaha?

borkdude19:01:00

this completion namespace has top-level futures, very bad

borkdude19:01:17

I made a fork of this stuff to make reply (the lein REPL) work with graalvm

borkdude19:01:34

and I turned the futures into delays

ericdallo19:01:15

do you have the code, it'd be helpful to fix on nrepl too, right?

borkdude19:01:11

I just added you to the repo

borkdude19:01:21

I haven't open sourced the code since it's quite hacky

๐Ÿ‘ 3
borkdude19:01:40

and copy/pasted the entire reply lib to hack on it

ericdallo19:01:04

you have to many repos ๐Ÿ˜‚ that's crazy and awesome at same time ๐Ÿ˜›

borkdude19:01:09

@UKFSJSM38 what you can maybe do is copy paste the nrepl completions namespace into your own repo

borkdude19:01:17

and then change the futures into delays

borkdude19:01:42

(or move nrepl to a dev dev... or some feature toggle)

ericdallo19:01:06

moving to dev deps maybe is enough and the recommended IMO, I'll try to move to another profile so

borkdude19:01:35

this will also yield a smaller native image

ericdallo19:01:37

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

ericdallo20:01:26

@borkdude it compiles clojure-lsp perfect, is just that it just exit the app, do I need to make anything more?

ericdallo20:01:38

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.

ericdallo21:01:52

Ok, after adding H:DynamicProxyConfigurationResources with those classes it seems to work ๐Ÿ˜„ , let me test it

ericdallo00:01:00

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 ๐Ÿคž

ericdallo23:01:09

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 interface

ericdallo23:01:29

I compiled with graalvm 21 to check if there anything, but still the same

borkdude23:01:23

cool. I have no clue and I'm going to bed soon. keep me posted

yes 3
๐Ÿ‘‹ 3
ericdallo12:01:21

I 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 #{}

borkdude12:01:43

So what does this tell you?

ericdallo13:01:55

It means the for some reason, graalvm is not working with that reify

borkdude13:01:17

@UKFSJSM38 what error do you get when using it?

ericdallo13:01:43

Jan 27, 2021 9:46:32 AM org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint request
WARNING: Unsupported request method: initialize

ericdallo13:01:23

because it can't find the implementation of one of the methods initialize

borkdude13:01:49

because you haven't reified it?

borkdude13:01:01

oh sorry I misread

ericdallo13:01:46

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

borkdude13:01:09

@UKFSJSM38 Maybe this will work: Is it possible to make the reified object at the top-level, at compile time?

ericdallo13:01:46

yes it's possible, changing from defn to def would be enough?

ericdallo13:01:38

Let me try it loading

ericdallo13:01:41

Same issue, this is the output of that print from above:

--> class clojure_lsp.main$reify__31521 #{}

borkdude13:01:15

also the same error when used?

borkdude13:01:48

or add the class to the reflection config

Shantanu Kumar13:01:54

@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.

ericdallo13:01:55

But how can I get the class name? or you mean add the interface to the reflection config?

ericdallo13:01:34

good point @kumarshantanu, I'll need type hint for other methods later, but for that test it's valid, let me try

borkdude13:01:38

yeah, the interface

๐Ÿ‘ 4
ericdallo13:01:06

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

ericdallo13:01:27

Not sure is related to remove the type hinting or to move to the reflection.json

borkdude13:01:41

never change two things at a time ;)

ericdallo13:01:15

it seems another issue now, it's serializing the json to respond to client

ericdallo13:01:31

Probably I should do something to initialize Gson correctly

ericdallo13:01:21

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 

ericdallo13:01:36

should I add it to reflection.json?

ericdallo13:01:23

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 ๐Ÿคž

ericdallo14:01:04

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

ericdallo14:01:34

Should I add com.google.gson.JsonObject to reflection too?

borkdude15:01:07

at least that method

ericdallo15:01:14

After that I get:

java.lang.NoSuchMethodError: java.lang.reflect.AccessibleObject.canAccess(java.lang.Object)
Not sure what to add to reflection.json now

borkdude15:01:26

Maybe java.lang.reflect.AccessibleObject ๐Ÿ˜†

ericdallo15:01:46

Yeah, that was my guess ๐Ÿ˜› compiling loading

borkdude15:01:04

you 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

borkdude15:01:15

it will also spit out a lot of classes you don't need, but it might give you some idea

borkdude15:01:23

be sure to run with direct linking, this might help

ericdallo15:01:40

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

borkdude15:01:33

I mean, this is just a setting when creating the uberjar

borkdude15:01:43

-J-Dclojure.compiler.direct-linking=true

borkdude15:01:58

I see, never mind

ericdallo15:01:33

np, thanks for the tip ๐Ÿ™‚

ericdallo18:01:18

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

Shantanu Kumar19:01:28

@UKFSJSM38 Try hinting dir as a public interface? (.getParent ^java.nio.file.Path dir)

ericdallo19:01:12

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

borkdude19:01:28

@UKFSJSM38 You should never have to include OS specific things in your reflection config, this config won't work on Windows for example

borkdude19:01:12

either for the return type or for the local

ericdallo19:01:51

You are right, something like that?

[dir ^java.nio.file.Path  (shared/uri->path project-root)]
@borkdude

borkdude19:01:21

yes, but if you place this type hint on your function it would be even better

โž• 3
๐Ÿ‘ 3
ericdallo14:01:15

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 ๐Ÿ˜•

Shantanu Kumar15:01:03

@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}

borkdude15:01:06

this won't help for java code which uses reflection

โœ”๏ธ 3
borkdude15:01:24

for this you can use the graalvm agent

ericdallo15:01:06

Yep, that's the agent that should be used with graal's java that is not available for NixOs :/

borkdude15:01:46

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?

ericdallo15:01:54

I can if it don't use any other lib

ericdallo15:01:06

I will give a try so

borkdude15:01:16

yes, it's standalone. just unzip it and set GRAALVM_HOME to the thing and then use $GRAALVM_HOME/native-image

borkdude15:01:42

this is how I use multiple graalvm versions side by side

borkdude15:01:21

also run $GRAALVM_HOME/gu install native-image

borkdude15:01:04

$GRAALVM_HOME/bin/gu install native-image
to be precise

ericdallo16:01:06

thanks! but to run with graal's java it should be something like $GRAALVM_HOME/bin/java I guess

borkdude16:01:57

that's right. you can export GRAALVM_HOME on the path like export PATH="$GRAALVM_HOME/bin:$PATH"

borkdude16:01:09

and then just run java

๐Ÿ‘ 3
ericdallo16:01:17

It doesn't work, it depends on libs from SO ๐Ÿ˜•

borkdude16:01:33

This is what I install on CircleCI:

sudo apt-get -y install gcc g++ zlib1g-dev

borkdude16:01:47

I'm sure there is a nixOS incantation for this?

borkdude16:01:15

you can always run this in a docker I guess

ericdallo23:01:51

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:

ericdallo23:01:15

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)

borkdude23:01:05

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.

๐Ÿ‘ 3
borkdude23:01:27

-J-Dclojure.compiler.direct-linking=true

borkdude23:01:10

I'm afk now

๐Ÿ‘‹ 3
ericdallo01:01:43

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

ericdallo03:01:00

It was related with some instances not being recognized in runtime, fixed it using some type hints ๐Ÿ˜„

ericdallo03:01:36

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 โค๏ธ clojure-lsp

๐ŸŽ‰ 27
โค๏ธ 9
ericdallo18:01:08

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

borkdude18:01:33

@UKFSJSM38 I don't know what causes this. Where did you get the suggestions for the reflection / JNI config for sqlite?

borkdude18:01:09

@UKFSJSM38 Does his Java example work correctly when the db does not exist?

ericdallo18:01:27

Hum... I didn't test, good point, I'll test it

borkdude18:01:51

Also try graalvm java 8 instead of 11

ericdallo19:01:18

reflection not really

ericdallo19:01:22

Oh, just found some classes missing from the fix, I don't know why, I'll add them too

ericdallo19:01:54

Oh, yeah, so it's enough probably

ericdallo19:01:02

I realized it's missing the Throwable class

ericdallo19:01:16

since when don't find the db it should throw an exception, may be related

borkdude19:01:15

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

ericdallo19:01:25

Hum, interesting

ericdallo19:01:05

Yep, it fixed the issue ๐Ÿ™‚ Probably it was the missing Throwable... that is not on that repo master, only on that specific commit

ericdallo19:01:35

a segfault like that is really hard to debug...

borkdude19:01:32

this is why you should probably not just switch to native, but offer this as something experimental ;)

borkdude19:01:40

and keep offering the JVM jar as well

borkdude19:01:57

among other reasons

borkdude19:01:28

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)

borkdude19:01:11

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)?

ericdallo19:01:19

Yeah, My idea is if everything work provides that as experimental for some time

borkdude19:01:12

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

ericdallo19:01:24

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

ericdallo20:01:05

Yes, I have in mind setup a CI for the 3 OS building via GH actions, just like babashka

clojure-spin 3
ericdallo20:01:29

@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?

ericdallo20:01:23

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

borkdude20:01:07

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))]
  ...

borkdude20:01:29

note that it's best to keep this at the top level so it's not resolved at run time

borkdude20:01:39

the native-image will compile this away

borkdude20:01:20

and then of course you move the nrepl deps to a dev profile

ericdallo20:01:46

got it, do you use anything like that in your projects?

borkdude20:01:07

yeah, this is how all the features in babashka are implemented

borkdude20:01:17

see build.md, feature flags

ericdallo20:01:00

cool, thanks!

ericdallo20:01:54

that dynaload seems to fit perfect this use case

ericdallo20:01:00

should I wrap the dynaload call with a try catch to check if nrepl is enabled? Or is better to check a env var?

borkdude20:01:57

What you can do is do a require based on an env var. And then use dynaload to use the var(s)

ericdallo20:01:23

what is the point to use dynaload if I can just require 'mylist ? I think I don't get it

ericdallo20:01:58

Oh, it won't compile if I don't require it.. it makes sense

ericdallo20:01:08

dynaload just use a symbol and do the magic

borkdude20:01:10

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

borkdude20:01:33

in normal JVM dynaload will also do the require for you

borkdude20:01:40

but for GraalVM this has a negative impact on binary size

borkdude20:01:52

although it will still work

borkdude20:01:07

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.

ericdallo16:01:52

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?

borkdude16:01:45

@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

borkdude16:01:04

As for the image size, you can look at the analysis callgraph or use the dashboard options.

ericdallo16:01:58

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.

ericdallo16:01:12

How can I check the analysis callgraph?

borkdude16:01:50

"-H:+PrintAnalysisCallTree"

borkdude16:01:01

this will dump some information about the pulled in classes, methods, etc

ericdallo16:01:17

Nice, I'll try

borkdude16:01:41

I haven't used the dashboard that much, but it seems useful. You can drill down into classes

ericdallo20:01:30

@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 -&gt; 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)

ericdallo20:01:59

Also, I added the new -H:+InlineBeforeAnalysis flag, not sure this could cause that performance improvement though

borkdude20:01:14

@UKFSJSM38 Be sure to use the --no-fallback option so you won't get a fallback image, this is effectively just a JVM

borkdude20:01:41

And yeah, the dashboard data may require moar memory

borkdude20:01:45

it's a lot of data

ericdallo20:01:08

yeah, I'm using the --no-fallback, I don't want that kind of lie anymore ๐Ÿ˜‚

borkdude20:01:43

You could also post an issue about a performance regression on the graalvm issue tracker

ericdallo20:01:35

@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

ericdallo20:01:12

so it's not a regression since I went from 20.2.0 -> 21.0 ๐Ÿ˜„

borkdude20:01:22

so 21 is faster right?

borkdude20:01:44

ok, then it's good!

ericdallo20:01:23

yep, at least for the only thing I noticed that it was slower than JVM, now it's not anymore ๐Ÿ˜„

ericdallo01:01:29

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

ericdallo03:01:36

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 โค๏ธ clojure-lsp

๐ŸŽ‰ 27
โค๏ธ 9