Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Native Language Server integration with PM #11880

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from

Conversation

hubertp
Copy link
Collaborator

@hubertp hubertp commented Dec 16, 2024

Pull Request Description

The first attempt at

  1. Building enso native-image that includes full Language Server
  2. Integrating the executable as an experimental feature during project startup

The change (for now) assumes that enso executable appears in Enso's default engines directory.
To build and run the new integration one has to

  1. engine-runner/buildNativeImage
  2. Run PM with --native-language-server

This change also adds a copy of some of logback's code (a simple socket server) as it was impossible to debug serialization bugs without some additional logging. Potentially to be removed in the final PR.

This is by no means a final change in this area. We need to make it possible to include all other Standard libs, at least. But it's a step forward that allows devs to experiment.

Important Notes

There is a change in JsonConnectionController which only sets the controller as initialized when all resources are set up. Previously, it seems, the asynchronous setup could lead to some race conditions.

Exchange between LS and GUI (before):
Screenshot from 2024-12-30 15-17-30

(after)
Screenshot from 2024-12-30 15-20-08

As one can see, speedup in startup is rather massive.

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.

The first attempt at
a) building `enso` native-image that includes full Language Server
b) integrating the executable as an experimental feature during project
startup

The change (for now) assumes that `enso` executable appears in Enso's
default `engines` directory.
To build and run the new integration one has to
a) `engine-runner/buildNativeImage`
b) run PM with `--native-language-server`

This change also adds a copy of some of logback's code (`SocketAppender`
or a simple socket server`) as it was impossible to debug serialization
bugs without some additional logging.
@hubertp hubertp added the CI: No changelog needed Do not require a changelog entry for this PR. label Dec 16, 2024
@hubertp hubertp marked this pull request as ready for review January 2, 2025 09:18
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I am glad to see language server startup improvement
  • that's what native image technology is good for and I am glad to see it delivers on its promise
  • for a while I was confused by the dependency changes
  • looks like it is mostly to access TruffleOptions.AOT from the non-Truffle part of our codebase
  • please keep Truffle API out of the dependencies of launcher, boot, etc. projects
  • use Boolean.getBoolean("com.oracle.graalvm.isaot") directly
  • I suggest to avoid introduction of --native-... CLI options
    • we may not need them at all at this stage
    • just prefer bin/enso or bin\enso.exe native binary when it exists
    • there already is well functioning --jvm option
    • we may want to expose it from project manager CLI, but
    • it is already accessible via ENSO_OPTS and that may be enough for now
  • I suggest to separate separate *-config.json and put them into language-server source tree
  • I am glad to see the Launching Enso programs instantly #10121 work moving forward

build.sbt Outdated
Compile / moduleDependencies ++= {
Seq(
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion,
"org.slf4j" % "slf4j-api" % slf4jVersion
)
) ++ GraalVM.modules.map(_.withConfigurations(Some(Runtime.name)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ScalaDoc of engine-common project which is available few lines above this change notes:

As such it needs to depend on org.graalvm.polyglot package. Otherwise
its dependencies shall be limited

As far as I can see GraalVM.modules contains Truffle API and that's not needed to boot Context - in fact that is an API that users (those not implementing a language) of the GraalVM shouldn't see at all.

What is the reason for these dependency changes?

"commons-io" % "commons-io" % commonsIoVersion,
"com.google.flatbuffers" % "flatbuffers-java" % flatbuffersVersion,
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need org-openide-util-lookup in the runtime. There is:

What is the failure you see when the dependency is missing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we only need @ServiceProvider annotation, then the org-openide-util-lookup library should only be % "provided".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependency has already been part of libraryDependencies and marked with provided classifier, yes.

I was getting class not found exception.

Copy link
Collaborator Author

@hubertp hubertp Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it's compile-time scope of moduleDependecies so that should be alright.

Copy link
Member

@JaroslavTulach JaroslavTulach Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CCing @Akirathan. I am not really sure about "compile time only" behavior of Compile / moduleDependencies. As far as I know Compile / moduleDependencies puts the JAR on --module-path as unnamed module (because org-openide-util-lookup isn't Java platform module). I don't think there is a reason for that - having it on classpath should be enough if we only need its annotation processor. Moreover having it on classpath with provided status prevents it to be included in the runtime JARs then.

It is a minor issue, but given:

build.sbt Outdated
@@ -2280,16 +2283,21 @@ lazy val `language-server` = (project in file("engine/language-server"))
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion,
"org.apache.tika" % "tika-core" % tikaVersion % Test
),
libraryDependencies ++= GraalVM.modules.map(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't language-server also a "client of GraalVM" - e.g. it shouldn't really need dependency on Truffle APIs? Not even runtime one?

@@ -3705,6 +3717,7 @@ lazy val `engine-runner` = project
"-H:IncludeResources=.*Main.enso$",
"-H:+AddAllCharsets",
"-H:+IncludeAllLocales",
"-H:+UnlockExperimentalVMOptions",
Copy link
Member

@JaroslavTulach JaroslavTulach Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the options we unlocking?

@@ -228,6 +232,19 @@ public Context build() {
.allowCreateThread(true);
}

if (engineOptions != null) {
// In AOT mode one must not use a shared engine; the latter causes issues when initializing
// message transport - it is set to `null`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I don't see a reason wny transport shouldn't work. Maybe it is a bug. Do we have a (small) reproducer that we could report to GraalVM guys and find out what they think?

@@ -108,7 +108,7 @@ public void onContextClosed(TruffleContext context) {}
@Override
protected void onCreate(Env env) {
this.env = env;
env.registerService(this);

if (TruffleOptions.AOT) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage of TruffleOptions.AOT is correct here as this is a Truffle instrument and it needs dependency on Truffle API.

val native: cli.Option = cli.Option.builder
.longOpt(NATIVE_OPTION)
.desc(
"(experimental) Attempts to use the native-image of any subprocess."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use the bin/enso or bin/enso.exe if it is generated without any (even experimental) option?

Btw. there already is support for --jvm option. We should align with it. Maybe just expose --jvm as an option to project-manager and just pass it on to bin/enso?

Anyway the same can already be done with ENSO_OPTS environment variable and it may be enough for development purposes.

val dm = new DistributionManager(env)
val execName = OS.executableName("enso")
val fullExecPath =
dm.paths.engines.resolve(version).resolve("bin").resolve(execName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove all the support of --native-image options and just checked for existence of this fullExecPath binary (probably check it is not a bash shell) and if present just invoke it. It should be good enough for development purposes, but please note that it is also correct for the future:

E.g. preferring the enso binary if it exists is the right solution for the future.

"--module-path",
componentPath.toString,
"-m",
"org.enso.runner/org.enso.runner.Main"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we rely on bin/enso native binaries, we will no longer be able to access Main directly. Such a "JVM switch" will be (and already is) handled by bin/enso when --jvm option is passed to them. E.g. this code should disappear at the end, and project manager shall always call into bin/enso.

Despite being advertised as such, native image doesn't set
`com.oracle.graalvm.isaot` and it has to be provided separately if we
don't want to use `TruffleOptions`.
@@ -0,0 +1,59 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: No changelog needed Do not require a changelog entry for this PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants