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 #{}
So what does this tell you?
It means the for some reason, graalvm is not working with that reify
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
@ericdallo 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
because it can't find the implementation of one of the methods initialize
because you haven't reified it?
oh sorry I misread
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@ericdallo Maybe this will work: Is it possible to make the reified object at the top-level, at compile time?
yes it's possible, changing from defn
to def
would be enough?
think so
Let me try it :loading:
Same issue, this is the output of that print from above:
--> class clojure_lsp.main$reify__31521 #{}
also the same error when used?
yep, maybe try with gen-class? https://stackoverflow.com/a/8614783
or add the class to the reflection config
@ericdallo 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
yeah, the interface
1π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
Not sure is related to remove the type hinting or to move to the reflection.json
never change two things at a time ;)
yeah hahaha
it seems another issue now, it's serializing the json to respond to client
Probably I should do something to initialize Gson correctly
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
should I add it to reflection.json?
guess so
1If 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
Should I add com.google.gson.JsonObject
to reflection too?
yes
at least that method
After that I get:
java.lang.NoSuchMethodError: java.lang.reflect.AccessibleObject.canAccess(java.lang.Object)
Not sure what to add to reflection.json nowMaybe java.lang.reflect.AccessibleObject
π
Yeah, that was my guess π compiling :loading:
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
it will also spit out a lot of classes you don't need, but it might give you some idea
be sure to run with direct linking, this might help
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
I mean, this is just a setting when creating the uberjar
-J-Dclojure.compiler.direct-linking=true
oh
I see, never mind
np, thanks for the tip π
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@ericdallo 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 <http://clojure.java.io|clojure.java.io>
instead of manually using java classes, similar how clj-kondo do to get the config dir
@ericdallo 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
either for the return type or for the local
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
1π1β