Skip to content

Commit 7f7d0d1

Browse files
authored
Native Language Server integration with PM (#11880)
* Native Language Server integration with PM 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. * nit * Add missing reflect config Otherwise Akka and other code is simply dead-code eliminated in native-image. * Ensure proper Context construction When message transport is missing, context should be properly built, even in AOT mode. * Add missing Akka entries in reflect-config * nit * Partial revert * Fix service registration Problems revealed after a clean build * Fix Base_Tests in native-image * Fix compilation issues * Drop Truffle dependency * fmt * Drop TruffleOptions dependency 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`. * Move NI config to LS resources * Remove automatically added module dependency * Move AOT Context-creation logic to factory Context creation for AOT needs to define a separate Engine configuration rather than using a shared engine to be able to use message transport. * PR review
1 parent 2ead3f5 commit 7f7d0d1

File tree

29 files changed

+4515
-203
lines changed

29 files changed

+4515
-203
lines changed

build.sbt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2283,12 +2283,13 @@ lazy val `language-server` = (project in file("engine/language-server"))
22832283
javaModuleName := "org.enso.language.server",
22842284
Compile / moduleDependencies ++=
22852285
Seq(
2286-
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion,
2287-
"org.slf4j" % "slf4j-api" % slf4jVersion,
2288-
"commons-cli" % "commons-cli" % commonsCliVersion,
2289-
"commons-io" % "commons-io" % commonsIoVersion,
2290-
"com.google.flatbuffers" % "flatbuffers-java" % flatbuffersVersion,
2291-
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion
2286+
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion,
2287+
"org.slf4j" % "slf4j-api" % slf4jVersion,
2288+
"commons-cli" % "commons-cli" % commonsCliVersion,
2289+
"commons-io" % "commons-io" % commonsIoVersion,
2290+
"com.google.flatbuffers" % "flatbuffers-java" % flatbuffersVersion,
2291+
"org.eclipse.jgit" % "org.eclipse.jgit" % jgitVersion,
2292+
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion
22922293
),
22932294
Compile / internalModuleDependencies := Seq(
22942295
(`akka-wrapper` / Compile / exportedModule).value,
@@ -3614,13 +3615,17 @@ lazy val `engine-runner` = project
36143615
val epbLang =
36153616
(`runtime-language-epb` / Compile / fullClasspath).value
36163617
.map(_.data.getAbsolutePath)
3618+
val langServer =
3619+
(`language-server` / Compile / fullClasspath).value
3620+
.map(_.data.getAbsolutePath)
36173621
val core = (
36183622
runnerDeps ++
36193623
runtimeDeps ++
36203624
loggingDeps ++
36213625
replDebugInstr ++
36223626
runtimeServerInstr ++
36233627
idExecInstr ++
3628+
langServer ++
36243629
epbLang
36253630
).distinct
36263631
val stdLibsJars =
@@ -3722,6 +3727,7 @@ lazy val `engine-runner` = project
37223727
"org.jline",
37233728
"io.methvin.watchservice",
37243729
"zio.internal",
3730+
"zio",
37253731
"org.enso.runner",
37263732
"sun.awt",
37273733
"sun.java2d",
@@ -3733,7 +3739,8 @@ lazy val `engine-runner` = project
37333739
"akka.http",
37343740
"org.enso.base",
37353741
"org.enso.image",
3736-
"org.enso.table"
3742+
"org.enso.table",
3743+
"org.eclipse.jgit"
37373744
)
37383745
)
37393746
}

engine/common/src/main/java/org/enso/common/ContextFactory.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.enso.logger.JulHandler;
1010
import org.enso.logging.config.LoggerSetup;
1111
import org.graalvm.polyglot.Context;
12+
import org.graalvm.polyglot.Engine;
1213
import org.graalvm.polyglot.HostAccess;
1314
import org.graalvm.polyglot.io.MessageTransport;
1415
import org.slf4j.event.Level;
@@ -52,6 +53,7 @@ public final class ContextFactory {
5253
private String checkForWarnings;
5354
private int warningsLimit = 100;
5455
private java.util.Map<String, String> options = new HashMap<>();
56+
private String runtimerServerKey;
5557
private boolean enableDebugServer;
5658

5759
private ContextFactory() {}
@@ -145,6 +147,11 @@ public ContextFactory options(Map<String, String> options) {
145147
return this;
146148
}
147149

150+
public ContextFactory enableRuntimeServerInfoKey(String keyName) {
151+
this.runtimerServerKey = keyName;
152+
return this;
153+
}
154+
148155
public ContextFactory checkForWarnings(String fqnOfMethod) {
149156
this.checkForWarnings = fqnOfMethod;
150157
return this;
@@ -161,6 +168,16 @@ public Context build() {
161168
}
162169
var julLogLevel = Converter.toJavaLevel(logLevel);
163170
var logLevelName = julLogLevel.getName();
171+
var inAOTMode = java.lang.Boolean.getBoolean("com.oracle.graalvm.isaot");
172+
java.util.Map<String, String> engineOptions = null;
173+
if (runtimerServerKey != null) {
174+
if (!inAOTMode) {
175+
options.put(runtimerServerKey, "true");
176+
} else {
177+
engineOptions = new java.util.HashMap<>();
178+
engineOptions.put(runtimerServerKey, "true");
179+
}
180+
}
164181
var builder =
165182
Context.newBuilder()
166183
.allowExperimentalOptions(true)
@@ -189,9 +206,6 @@ public Context build() {
189206
if (enableDebugServer) {
190207
builder.option(DebugServerInfo.ENABLE_OPTION, "true");
191208
}
192-
if (messageTransport != null) {
193-
builder.serverTransport(messageTransport);
194-
}
195209
builder.option(RuntimeOptions.LOG_LEVEL, logLevelName);
196210
var logHandler = JulHandler.get();
197211
var logLevels = LoggerSetup.get().getConfig().getLoggers();
@@ -228,6 +242,18 @@ public Context build() {
228242
.allowCreateThread(true);
229243
}
230244

245+
if (inAOTMode) {
246+
// In AOT mode one must not use a shared engine; the latter causes issues when initializing
247+
// message transport - it is set to `null`.
248+
var eng = Engine.newBuilder().allowExperimentalOptions(true).options(engineOptions);
249+
if (messageTransport != null) {
250+
eng.serverTransport(messageTransport);
251+
}
252+
builder.engine(eng.build());
253+
} else if (messageTransport != null) {
254+
builder.serverTransport(messageTransport);
255+
}
256+
231257
var ctx = builder.build();
232258
ContextInsightSetup.configureContext(ctx);
233259
return ctx;

engine/language-server/src/main/java/module-info.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
requires org.enso.task.progress.notifications;
3939
requires org.enso.ydoc.polyfill;
4040

41-
exports org.enso.languageserver.boot;
4241
exports org.enso.languageserver.filemanager to scala.library;
4342
exports org.enso.languageserver.runtime to scala.library;
4443
exports org.enso.languageserver.search to scala.library;

engine/language-server/src/main/java/org/enso/languageserver/boot/LanguageServerRunner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.slf4j.event.Level;
99
import scala.concurrent.ExecutionContext;
1010

11+
@org.openide.util.lookup.ServiceProvider(service = LanguageServerApi.class)
1112
public final class LanguageServerRunner extends LanguageServerApi {
1213
public LanguageServerRunner() {}
1314

engine/language-server/src/main/java/org/enso/languageserver/boot/resource/RepoInitialization.java

Lines changed: 16 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package org.enso.languageserver.boot.resource;
22

33
import akka.event.EventStream;
4-
import java.io.IOException;
5-
import java.nio.file.FileSystemException;
6-
import java.nio.file.Files;
7-
import java.nio.file.NoSuchFileException;
8-
import java.util.concurrent.*;
9-
import org.apache.commons.io.FileUtils;
4+
import java.util.concurrent.CompletableFuture;
5+
import java.util.concurrent.CompletionStage;
6+
import java.util.concurrent.Executor;
7+
import java.util.concurrent.Semaphore;
108
import org.enso.languageserver.data.ProjectDirectoriesConfig;
119
import org.enso.languageserver.event.InitializedEvent;
12-
import org.enso.logger.masking.MaskedPath;
1310
import org.enso.searcher.memory.InMemorySuggestionsRepo;
1411
import org.slf4j.Logger;
1512
import org.slf4j.LoggerFactory;
@@ -72,14 +69,18 @@ public CompletableFuture<Void> init() {
7269
}
7370

7471
private CompletableFuture<Void> initSuggestionsRepo() {
75-
return CompletableFuture.runAsync(
76-
() -> logger.debug("Initializing suggestions repo [{}]...", suggestionsRepo), executor)
77-
.thenComposeAsync(
78-
v -> {
79-
if (!isInitialized)
80-
return doInitSuggestionsRepo()
81-
.exceptionallyComposeAsync(this::recoverInitializationError, executor);
82-
else return CompletableFuture.completedFuture(v);
72+
return CompletableFuture.supplyAsync(
73+
() -> {
74+
logger.debug("Initializing Suggestions repo [{}]...", suggestionsRepo);
75+
try {
76+
lock.acquire();
77+
if (!isInitialized)
78+
return doInitSuggestionsRepo()
79+
.exceptionallyComposeAsync(this::recoverInitializationError, executor);
80+
else return CompletableFuture.completedFuture(null);
81+
} catch (InterruptedException e) {
82+
throw new RuntimeException(e);
83+
}
8384
},
8485
executor)
8586
.thenRunAsync(
@@ -105,60 +106,6 @@ private CompletableFuture<Void> recoverInitializationError(Throwable error) {
105106
.thenComposeAsync(v -> doInitSuggestionsRepo(), executor);
106107
}
107108

108-
private CompletableFuture<Void> clearDatabaseFile(int retries) {
109-
return CompletableFuture.runAsync(
110-
() -> {
111-
if (!isInitialized) {
112-
logger.debug("Clear database file. Attempt #{}", retries + 1);
113-
try {
114-
Files.delete(projectDirectoriesConfig.suggestionsDatabaseFile().toPath());
115-
} catch (IOException e) {
116-
throw new CompletionException(e);
117-
}
118-
}
119-
},
120-
executor)
121-
.exceptionallyComposeAsync(error -> recoverClearDatabaseFile(error, retries), executor);
122-
}
123-
124-
private CompletableFuture<Void> recoverClearDatabaseFile(Throwable error, int retries) {
125-
if (error instanceof CompletionException) {
126-
return recoverClearDatabaseFile(error.getCause(), retries);
127-
} else if (error instanceof NoSuchFileException) {
128-
logger.warn(
129-
"Failed to delete the database file. Attempt #{}. File does not exist [{}]",
130-
retries + 1,
131-
new MaskedPath(projectDirectoriesConfig.suggestionsDatabaseFile().toPath()));
132-
return CompletableFuture.completedFuture(null);
133-
} else if (error instanceof FileSystemException) {
134-
logger.error(
135-
"Failed to delete the database file. Attempt #{}. The file will be removed during the"
136-
+ " shutdown",
137-
retries + 1,
138-
error);
139-
Runtime.getRuntime()
140-
.addShutdownHook(
141-
new Thread(
142-
() ->
143-
FileUtils.deleteQuietly(projectDirectoriesConfig.suggestionsDatabaseFile())));
144-
return CompletableFuture.failedFuture(error);
145-
} else if (error instanceof IOException) {
146-
logger.error("Failed to delete the database file. Attempt #{}", retries + 1, error);
147-
if (retries < MAX_RETRIES) {
148-
try {
149-
Thread.sleep(RETRY_DELAY_MILLIS);
150-
} catch (InterruptedException e) {
151-
throw new CompletionException(e);
152-
}
153-
return clearDatabaseFile(retries + 1);
154-
} else {
155-
return CompletableFuture.failedFuture(error);
156-
}
157-
}
158-
159-
return CompletableFuture.completedFuture(null);
160-
}
161-
162109
private CompletionStage<Void> doInitSuggestionsRepo() {
163110
return FutureConverters.asJava(suggestionsRepo.init()).thenAcceptAsync(res -> {}, executor);
164111
}

0 commit comments

Comments
 (0)