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

Pre compute suggestion db during build time #5698

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b95a302
build: indexStdLib
4e6 Feb 9, 2023
6bb4d43
feat: serialization manager
4e6 Feb 14, 2023
9c84027
feat: update EnsureCompiledJob
4e6 Feb 15, 2023
b84603e
feat: simplify suggestions init
4e6 Feb 15, 2023
1ee00d3
misc: cleanup
4e6 Feb 20, 2023
e2ac361
Serialize provided suggestions to each standard library
JaroslavTulach Feb 20, 2023
d62ec42
Demo of iteratively adding constructing Tree
JaroslavTulach Feb 21, 2023
560a107
Merge remote-tracking branch 'origin/develop' into wip/db/5068-pre-co…
JaroslavTulach Feb 22, 2023
b68da75
Only regenerate index when necessary
hubertp Feb 22, 2023
8f7314e
Merge branch 'wip/db/5068-pre-compute-suggestion-db-during-build-time…
JaroslavTulach Feb 22, 2023
2fbdf75
Using Jackson to serialize and deserialize Suggestion.Module
JaroslavTulach Feb 23, 2023
f413b62
Can serialize Suggestion[]
JaroslavTulach Feb 23, 2023
958cc78
Can serialize record with List<Suggestion>
JaroslavTulach Feb 23, 2023
1ff4c25
Serialize and deserialize the Suggestion list as a case class
JaroslavTulach Feb 24, 2023
ce821b1
Can load back the Tree.Node[Suggestion] from a cache
JaroslavTulach Feb 24, 2023
5bff1ab
Less debug messages
JaroslavTulach Feb 24, 2023
f480e03
subscribe to Api.LibraryLoaded
JaroslavTulach Feb 24, 2023
335305a
Merge branch 'develop' into wip/db/5068-pre-compute-suggestion-db-dur…
4e6 Mar 2, 2023
b4a98a4
feat: suggestions cache
4e6 Mar 2, 2023
6638c9d
feat: suggestions deserialization
4e6 Mar 5, 2023
283b6f5
feat: generate docs
4e6 Mar 6, 2023
9081707
misc: format
4e6 Mar 6, 2023
0e769c1
fix: suggestions loading
4e6 Mar 6, 2023
6a96e38
misc: run deserialization in background
4e6 Mar 6, 2023
77c5a2b
test: fix language-server
4e6 Mar 6, 2023
009b595
test: fix runtime
4e6 Mar 6, 2023
31773a6
Merge branch 'develop' into wip/db/5068-pre-compute-suggestion-db-dur…
4e6 Mar 6, 2023
a45898a
test: fix RuntimeServerTest
4e6 Mar 6, 2023
5314e10
test: fix RuntimeErrorsTest
4e6 Mar 6, 2023
1447ed0
test: fix RuntimeInstrumentTest
4e6 Mar 6, 2023
20c2e40
test: fix RuntimeSuggestionUpdatesTest
4e6 Mar 6, 2023
89739c0
test: fix RuntimeStdlibTest
4e6 Mar 6, 2023
36862c6
test: fix RuntimeComponentsTest
4e6 Mar 6, 2023
3468399
misc: cleanup
4e6 Mar 6, 2023
952f0f6
Merge branch 'develop' into wip/db/5068-pre-compute-suggestion-db-dur…
mergify[bot] Mar 8, 2023
4742f5f
misc: review comments
4e6 Mar 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ final class SuggestionsHandler(
.subscribe(self, classOf[Api.ExpressionUpdates])
context.system.eventStream
.subscribe(self, classOf[Api.SuggestionsDatabaseModuleUpdateNotification])
context.system.eventStream.subscribe(
self,
classOf[Api.SuggestionsDatabaseSuggestionsLoadedNotification]
)
context.system.eventStream
.subscribe(self, classOf[Api.LibraryLoaded])
context.system.eventStream.subscribe(self, classOf[ProjectNameChangedEvent])
Expand Down Expand Up @@ -204,6 +208,9 @@ final class SuggestionsHandler(
initialized(projectName, graph, clients - client.clientId, state)
)

case msg: Api.SuggestionsDatabaseSuggestionsLoadedNotification =>
applyLoadedSuggestions(msg.suggestions)

case msg: Api.SuggestionsDatabaseModuleUpdateNotification
if state.isSuggestionUpdatesRunning =>
state.suggestionUpdatesQueue.enqueue(msg)
Expand Down Expand Up @@ -517,6 +524,48 @@ final class SuggestionsHandler(
}
}

/** Handle the suggestions of the loaded library.
*
* Adds the new suggestions to the suggestions database and sends the
* appropriate notification to the client.
*
* @param suggestions the loaded suggestions
* @return the API suggestions database update notification
*/
private def applyLoadedSuggestions(
suggestions: Vector[Suggestion]
): Future[SuggestionsDatabaseUpdateNotification] = {
for {
treeResults <- suggestionsRepo.applyTree(
suggestions.map(Api.SuggestionUpdate(_, Api.SuggestionAction.Add()))
)
version <- suggestionsRepo.currentVersion
} yield {
val treeUpdates = treeResults.flatMap {
case QueryResult(ids, Api.SuggestionUpdate(suggestion, action)) =>
val verb = action.getClass.getSimpleName
Copy link
Collaborator

Choose a reason for hiding this comment

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

can be moved to the condition with the log using it?

action match {
case Api.SuggestionAction.Add() =>
if (ids.isEmpty) {
logger.error("Cannot {} [{}].", verb, suggestion)
}
ids.map(
SuggestionsDatabaseUpdate.Add(
_,
generateDocumentation(suggestion)
)
)
case action =>
logger.error(
s"Invalid action during suggestions loading [$action]."
)
Seq()
}
}
SuggestionsDatabaseUpdateNotification(version, treeUpdates)
}
}

/** Handle the suggestions database update.
*
* Function applies notification updates on the suggestions database and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ object Runtime {
value = classOf[Api.SuggestionsDatabaseModuleUpdateNotification],
name = "suggestionsDatabaseModuleUpdateNotification"
),
new JsonSubTypes.Type(
value = classOf[Api.SuggestionsDatabaseSuggestionsLoadedNotification],
name = "suggestionsDatabaseSuggestionsLoadedNotification"
),
new JsonSubTypes.Type(
value = classOf[Api.AnalyzeModuleInScopeJobFinished],
name = "analyzeModuleInScopeJobFinished"
Expand Down Expand Up @@ -260,6 +264,10 @@ object Runtime {
new JsonSubTypes.Type(
value = classOf[Api.LockReleaseFailed],
name = "lockReleaseFailed"
),
new JsonSubTypes.Type(
value = classOf[Api.DeserializeLibrarySuggestions],
name = "deserializeLibrarySuggestions"
)
)
)
Expand Down Expand Up @@ -1506,6 +1514,22 @@ object Runtime {
")"
}

/** A notification about the suggestions of the loaded library.
*
* @param suggestions the loaded suggestions
*/
final case class SuggestionsDatabaseSuggestionsLoadedNotification(
suggestions: Vector[Suggestion]
) extends ApiNotification
with ToLogString {

/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"SuggestionsDatabaseSuggestionsLoadedNotification(" +
s"suggestions=${suggestions.map(_.toLogString(shouldMask))}" +
")"
}

/** A notification about the finished background analyze job. */
final case class AnalyzeModuleInScopeJobFinished() extends ApiNotification

Expand Down Expand Up @@ -1628,6 +1652,16 @@ object Runtime {
*/
final case class LockReleaseFailed(errorMessage: String) extends ApiResponse

/** A request to deserialize the library suggestions.
*
* Does not have a companion response message. The response will be
* delivered asynchronously as a notification.
*
* @param libraryName the name of the loaded library.
*/
final case class DeserializeLibrarySuggestions(libraryName: LibraryName)
extends ApiRequest

private lazy val mapper = {
val factory = new CBORFactory()
val mapper = new ObjectMapper(factory) with ClassTagExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected Optional<Metadata> metadataFromBytes(byte[] bytes) {
@Override
protected Optional<String> computeDigest(CachedSuggestions entry, TruffleLogger logger) {
var digest = messageDigest();
entry.getSuggestions().getSuggestions().forEach(suggestion -> {
entry.getSuggestions().forEach(suggestion -> {
ByteBuffer bytes = ByteBuffer.allocate(Integer.BYTES).putInt(suggestion.hashCode());
digest.update(bytes);
});
Expand Down Expand Up @@ -107,7 +107,7 @@ protected Optional<Roots> getCacheRoots(EnsoContext context) {

@Override
protected Object extractObjectToSerialize(CachedSuggestions entry) {
return entry.getSuggestions();
return entry.getSuggestionsObjectToSerialize();
}

// Suggestions class is not a record because of a Frgaal bug leading to invalid compilation error.
Expand All @@ -125,7 +125,7 @@ public List<Suggestion> getSuggestions() {
}

// CachedSuggestions class is not a record because of a Frgaal bug leading to invalid compilation error.
Copy link
Member

@JaroslavTulach JaroslavTulach Mar 6, 2023

Choose a reason for hiding this comment

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

What is the error? Maybe updating to new version of frgaal would fix the problem. If I modify:

enso$ git diff
diff --git engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java
index f5d65b8e0..220c01e9c 100644
--- engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java
+++ engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java
@@ -125,16 +125,7 @@ public final class SuggestionsCache
   }
 
   // CachedSuggestions class is not a record because of a Frgaal bug leading to invalid compilation error.
-  public final static class CachedSuggestions {
-
-    private final LibraryName libraryName;
-    private final Suggestions suggestions;
-
-    public CachedSuggestions(LibraryName libraryName, Suggestions suggestions) {
-      this.libraryName = libraryName;
-      this.suggestions = suggestions;
-    }
-
+  public record CachedSuggestions(LibraryName libraryName, Suggestions suggestions) {
     public LibraryName getLibraryName() {
       return libraryName;
     }

I am seeing:

sbt:enso> buildEngineDistribution
[info] compiling 45 Scala sources and 191 Java sources to /enso/engine/runtime/target/scala-2.13/classes ...
[error] /enso/engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java:128:17: not found: type java
[error]   public record CachedSuggestions(LibraryName libraryName, Suggestions suggestions) {
[error]                 ^
[error] one error found

That's a wild error. Reported to the frgaal project.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, this is what I was seeing as well and the reason why in other cases I don't use the record. Thanks for reporting it with frgaal

final static class CachedSuggestions {
public final static class CachedSuggestions {

private final LibraryName libraryName;
private final Suggestions suggestions;
Expand All @@ -139,9 +139,13 @@ public LibraryName getLibraryName() {
return libraryName;
}

public Suggestions getSuggestions() {
public Suggestions getSuggestionsObjectToSerialize() {
return suggestions;
}

public List<Suggestion> getSuggestions() {
return suggestions.getSuggestions();
}
}

record Metadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ class Compiler(
}
}

/** @return the serialization manager instance. */
def getSerializationManager: SerializationManager =
serializationManager

/** Processes the provided language sources, registering any bindings in the
* given scope.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,33 @@ class SerializationManager(compiler: Compiler) {
}
}

def deserializeSuggestions(
libraryName: LibraryName
): Option[SuggestionsCache.CachedSuggestions] = {
if (isWaitingForSerialization(libraryName)) {
abort(libraryName)
None
} else {
while (isSerializingLibrary(libraryName)) {
Thread.sleep(100)
}
new SuggestionsCache(libraryName).load(compiler.context).toScala match {
case result @ Some(_: SuggestionsCache.CachedSuggestions) =>
logger.log(
Level.FINE,
s"Restored suggestions for library [$libraryName]."
)
result
case _ =>
logger.log(
Level.FINEST,
s"Unable to load suggestions for library [$libraryName]."
)
None
}
}
}

def deserializeLibraryBindings(
libraryName: LibraryName
): Option[ImportExportCache.CachedBindings] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import org.graalvm.polyglot.io.MessageEndpoint
import java.nio.ByteBuffer
import scala.concurrent.Future

/** A message endpoint implementation used by the
* [[org.enso.interpreter.instrument.RuntimeServerInstrument]].
*/
/** A message endpoint implementation. */
class Endpoint(handler: Handler)
extends MessageEndpoint
with RuntimeServerConnectionEndpoint {
Expand Down Expand Up @@ -47,6 +45,15 @@ class Endpoint(handler: Handler)
def sendToClient(msg: Api.Response): Unit =
client.sendBinary(Api.serialize(msg))

/** Sends a notification to the runtime.
*
* Can be used to start a command processing in the background.
*
* @param msg the message to send.
*/
def sendToSelf(msg: Api.Request): Unit =
client.sendBinary(Api.serialize(msg))

/** Sends a request to the connected client and expects a reply. */
override def sendRequest(msg: ApiRequest): Future[ApiResponse] =
reverseRequestEndpoint.sendRequest(msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,21 @@ object NotificationHandler {
libraryName: LibraryName,
libraryVersion: LibraryVersion,
location: Path
): Unit = sendMessage(
Api.LibraryLoaded(
namespace = libraryName.namespace,
name = libraryName.name,
version = libraryVersion.toString,
location = location.toFile
): Unit = {
sendMessage(
Api.LibraryLoaded(
namespace = libraryName.namespace,
name = libraryName.name,
version = libraryVersion.toString,
location = location.toFile
)
)
Copy link
Member

Choose a reason for hiding this comment

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

I thought that the Api.LibraryLoaded should be enhanced with optional list of suggestions. However it looks like the proposal is to send a message to self and deserialize the suggestions later. Assuming the deserialization is instant - is there any benefit of sending the suggestions in a separate message?

)
endpoint.sendToSelf(
Api.Request(
Api.DeserializeLibrarySuggestions(libraryName)
)
)
}

/** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ object CommandFactory {
case _: Api.GetTypeGraphRequest =>
new GetTypeGraphCommand(request.requestId)

case payload: Api.DeserializeLibrarySuggestions =>
new DeserializeLibrarySuggestionsCmd(request.requestId, payload)

case Api.ShutDownRuntimeServer() =>
throw new IllegalArgumentException(
"ShutDownRuntimeServer request is not convertible to command object"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.enso.interpreter.instrument.command

import org.enso.interpreter.instrument.execution.RuntimeContext
import org.enso.interpreter.instrument.job.DeserializeLibrarySuggestionsJob
import org.enso.polyglot.runtime.Runtime.Api

import scala.concurrent.{ExecutionContext, Future}

/** A command that initiates the deserialization of suggestions.
*
* @param maybeRequestId an option with request id
*/
class DeserializeLibrarySuggestionsCmd(
maybeRequestId: Option[Api.RequestId],
request: Api.DeserializeLibrarySuggestions
) extends Command(maybeRequestId) {

/** @inheritdoc */
override def execute(implicit
ctx: RuntimeContext,
ec: ExecutionContext
): Future[Unit] =
ctx.jobProcessor.run(
new DeserializeLibrarySuggestionsJob(request.libraryName)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.enso.interpreter.instrument.job

import org.enso.editions.LibraryName
import org.enso.interpreter.instrument.execution.RuntimeContext
import org.enso.polyglot.runtime.Runtime.Api

import scala.jdk.CollectionConverters._

/** A job responsible for deserializing suggestions of loaded library.
*
* @param libraryName the name of loaded library
*/
final class DeserializeLibrarySuggestionsJob(
libraryName: LibraryName
) extends Job[Unit](
List(),
isCancellable = false,
mayInterruptIfRunning = false
) {

/** @inheritdoc */
override def run(implicit ctx: RuntimeContext): Unit = {
val serializationManager =
ctx.executionService.getContext.getCompiler.getSerializationManager
serializationManager
.deserializeSuggestions(libraryName)
.foreach { cachedSuggestions =>
ctx.endpoint.sendToClient(
Api.Response(
Api.SuggestionsDatabaseSuggestionsLoadedNotification(
cachedSuggestions.getSuggestions.asScala.toVector
)
)
)
}
}
}