mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
When opening the project for a second time suggestion database is not sent (#7699)
close #7413 Changelog: - update: the language server listens for the client disconnection event and invalidates the suggestions index # Important Notes The component browser contains suggestion entries after the refresh https://github.com/enso-org/enso/assets/357683/bcebb8bf-e09f-4fb0-86cf-95ced58413f3
This commit is contained in:
parent
255b424b72
commit
eefe74ed93
@ -305,6 +305,7 @@ class JsonConnectionController(
|
||||
sender() ! ResponseError(Some(id), SessionAlreadyInitialisedError)
|
||||
|
||||
case MessageHandler.Disconnected =>
|
||||
logger.info("Json session terminated [{}].", rpcSession.clientId)
|
||||
context.system.eventStream.publish(JsonSessionTerminated(rpcSession))
|
||||
context.stop(self)
|
||||
|
||||
|
@ -5,7 +5,8 @@ import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.languageserver.data.{ClientId, Config}
|
||||
import org.enso.languageserver.event.{
|
||||
ExecutionContextCreated,
|
||||
ExecutionContextDestroyed
|
||||
ExecutionContextDestroyed,
|
||||
JsonSessionTerminated
|
||||
}
|
||||
import org.enso.languageserver.monitoring.MonitoringProtocol.{Ping, Pong}
|
||||
import org.enso.languageserver.runtime.handler._
|
||||
@ -84,6 +85,8 @@ final class ContextRegistry(
|
||||
.subscribe(self, classOf[Api.ExecutionUpdate])
|
||||
context.system.eventStream
|
||||
.subscribe(self, classOf[Api.VisualizationEvaluationFailed])
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[JsonSessionTerminated])
|
||||
}
|
||||
|
||||
override def receive: Receive =
|
||||
@ -346,6 +349,24 @@ final class ContextRegistry(
|
||||
} else {
|
||||
sender() ! AccessDenied
|
||||
}
|
||||
|
||||
case JsonSessionTerminated(client) =>
|
||||
store.contexts.getOrElse(client.clientId, Set()).foreach { contextId =>
|
||||
val handler = context.actorOf(
|
||||
DestroyContextHandler.props(
|
||||
runtimeFailureMapper,
|
||||
timeout,
|
||||
runtime
|
||||
)
|
||||
)
|
||||
store.getListener(contextId).foreach(context.stop)
|
||||
handler.forward(Api.DestroyContextRequest(contextId))
|
||||
context.become(
|
||||
withStore(store.removeContext(client.clientId, contextId))
|
||||
)
|
||||
context.system.eventStream
|
||||
.publish(ExecutionContextDestroyed(contextId, client.clientId))
|
||||
}
|
||||
}
|
||||
|
||||
private def getRuntimeStackItem(
|
||||
|
@ -16,7 +16,7 @@ import org.enso.languageserver.data.{
|
||||
Config,
|
||||
ReceivesSuggestionsDatabaseUpdates
|
||||
}
|
||||
import org.enso.languageserver.event.InitializedEvent
|
||||
import org.enso.languageserver.event.{InitializedEvent, JsonSessionTerminated}
|
||||
import org.enso.languageserver.filemanager.{
|
||||
ContentRootManager,
|
||||
FileDeletedEvent,
|
||||
@ -115,6 +115,8 @@ final class SuggestionsHandler(
|
||||
.subscribe(self, InitializedEvent.SuggestionsRepoInitialized.getClass)
|
||||
context.system.eventStream
|
||||
.subscribe(self, InitializedEvent.TruffleContextInitialized.getClass)
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[JsonSessionTerminated])
|
||||
}
|
||||
|
||||
override def receive: Receive =
|
||||
@ -313,6 +315,17 @@ final class SuggestionsHandler(
|
||||
suggestionsRepo.currentVersion
|
||||
.map(GetSuggestionsDatabaseResult(_, Seq()))
|
||||
.pipeTo(sender())
|
||||
if (state.shouldStartBackgroundProcessing) {
|
||||
runtimeConnector ! Api.Request(Api.StartBackgroundProcessing())
|
||||
context.become(
|
||||
initialized(
|
||||
projectName,
|
||||
graph,
|
||||
clients,
|
||||
state.backgroundProcessingStarted()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case Completion(path, pos, selfType, returnType, tags, isStatic) =>
|
||||
val selfTypes = selfType.toList.flatMap(ty => ty :: graph.getParents(ty))
|
||||
@ -397,6 +410,14 @@ final class SuggestionsHandler(
|
||||
)
|
||||
)
|
||||
action.pipeTo(handler)(sender())
|
||||
context.become(
|
||||
initialized(
|
||||
projectName,
|
||||
graph,
|
||||
clients,
|
||||
state.backgroundProcessingStopped()
|
||||
)
|
||||
)
|
||||
|
||||
case ProjectNameUpdated(name, updates) =>
|
||||
updates.foreach(sessionRouter ! _)
|
||||
@ -434,6 +455,29 @@ final class SuggestionsHandler(
|
||||
state.suggestionLoadingComplete()
|
||||
)
|
||||
)
|
||||
|
||||
case JsonSessionTerminated(_) =>
|
||||
val action = for {
|
||||
_ <- suggestionsRepo.clean
|
||||
} yield SearchProtocol.InvalidateModulesIndex
|
||||
|
||||
val handler = context.system.actorOf(
|
||||
InvalidateModulesIndexHandler.props(
|
||||
RuntimeFailureMapper(contentRootManager),
|
||||
timeout,
|
||||
runtimeConnector
|
||||
)
|
||||
)
|
||||
|
||||
action.pipeTo(handler)
|
||||
context.become(
|
||||
initialized(
|
||||
projectName,
|
||||
graph,
|
||||
clients,
|
||||
state.backgroundProcessingStopped()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Transition the initialization process.
|
||||
@ -744,6 +788,12 @@ object SuggestionsHandler {
|
||||
_shouldStartBackgroundProcessing = false
|
||||
this
|
||||
}
|
||||
|
||||
/** @return the new state with the background processing stopped. */
|
||||
def backgroundProcessingStopped(): State = {
|
||||
_shouldStartBackgroundProcessing = true
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a configuration object used to create a [[SuggestionsHandler]].
|
||||
|
@ -0,0 +1,66 @@
|
||||
package org.enso.interpreter.instrument.command;
|
||||
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import org.enso.interpreter.instrument.execution.RuntimeContext;
|
||||
import org.enso.interpreter.instrument.job.DeserializeLibrarySuggestionsJob;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.polyglot.runtime.Runtime$Api$InvalidateModulesIndexResponse;
|
||||
import scala.Option;
|
||||
import scala.concurrent.ExecutionContext;
|
||||
import scala.concurrent.Future;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
/** A command that invalidates the modules index. */
|
||||
public final class InvalidateModulesIndexCommand extends AsynchronousCommand {
|
||||
|
||||
/**
|
||||
* Create a command that invalidates the modules index.
|
||||
*
|
||||
* @param maybeRequestId an option with request id
|
||||
*/
|
||||
public InvalidateModulesIndexCommand(Option<UUID> maybeRequestId) {
|
||||
super(maybeRequestId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<BoxedUnit> executeAsynchronously(RuntimeContext ctx, ExecutionContext ec) {
|
||||
return Future.apply(
|
||||
() -> {
|
||||
TruffleLogger logger = ctx.executionService().getLogger();
|
||||
long writeCompilationLockTimestamp = ctx.locking().acquireWriteCompilationLock();
|
||||
try {
|
||||
ctx.jobControlPlane().abortAllJobs();
|
||||
|
||||
EnsoContext context = ctx.executionService().getContext();
|
||||
context.getTopScope().getModules().forEach(module -> module.setIndexed(false));
|
||||
ctx.jobControlPlane().stopBackgroundJobs();
|
||||
|
||||
context
|
||||
.getPackageRepository()
|
||||
.getLoadedPackages()
|
||||
.foreach(
|
||||
pkg -> {
|
||||
ctx.jobProcessor()
|
||||
.runBackground(new DeserializeLibrarySuggestionsJob(pkg.libraryName()));
|
||||
return BoxedUnit.UNIT;
|
||||
});
|
||||
|
||||
reply(new Runtime$Api$InvalidateModulesIndexResponse(), ctx);
|
||||
} finally {
|
||||
ctx.locking().releaseWriteCompilationLock();
|
||||
logger.log(
|
||||
Level.FINEST,
|
||||
"Kept write compilation lock [{0}] for {1} milliseconds.",
|
||||
new Object[] {
|
||||
this.getClass().getSimpleName(),
|
||||
System.currentTimeMillis() - writeCompilationLockTimestamp
|
||||
});
|
||||
}
|
||||
|
||||
return BoxedUnit.UNIT;
|
||||
},
|
||||
ec);
|
||||
}
|
||||
}
|
@ -56,8 +56,8 @@ object CommandFactory {
|
||||
case payload: Api.SetExpressionValueNotification =>
|
||||
new SetExpressionValueCmd(payload)
|
||||
|
||||
case payload: Api.InvalidateModulesIndexRequest =>
|
||||
new InvalidateModulesIndexCmd(request.requestId, payload)
|
||||
case _: Api.InvalidateModulesIndexRequest =>
|
||||
new InvalidateModulesIndexCommand(request.requestId)
|
||||
|
||||
case _: Api.GetTypeGraphRequest =>
|
||||
new GetTypeGraphCommand(request.requestId)
|
||||
|
@ -1,35 +0,0 @@
|
||||
package org.enso.interpreter.instrument.command
|
||||
|
||||
import org.enso.interpreter.instrument.execution.RuntimeContext
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
/** A command that invalidates the modules index.
|
||||
*
|
||||
* @param maybeRequestId an option with request id
|
||||
* @param request a request for invalidation
|
||||
*/
|
||||
class InvalidateModulesIndexCmd(
|
||||
maybeRequestId: Option[Api.RequestId],
|
||||
val request: Api.InvalidateModulesIndexRequest
|
||||
) extends AsynchronousCommand(maybeRequestId) {
|
||||
|
||||
/** Executes a request.
|
||||
*
|
||||
* @param ctx contains suppliers of services to perform a request
|
||||
*/
|
||||
override def executeAsynchronously(implicit
|
||||
ctx: RuntimeContext,
|
||||
ec: ExecutionContext
|
||||
): Future[Unit] = {
|
||||
for {
|
||||
_ <- Future { ctx.jobControlPlane.abortAllJobs() }
|
||||
} yield {
|
||||
ctx.executionService.getContext.getTopScope.getModules
|
||||
.forEach(_.setIndexed(false))
|
||||
reply(Api.InvalidateModulesIndexResponse())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -30,6 +30,13 @@ trait JobControlPlane {
|
||||
*/
|
||||
def startBackgroundJobs(): Boolean
|
||||
|
||||
/** Stops background jobs processing.
|
||||
*
|
||||
* @return `true` if the call stopped background job, `false` if they are
|
||||
* already stopped.
|
||||
*/
|
||||
def stopBackgroundJobs(): Boolean
|
||||
|
||||
/** Finds the first in-progress job satisfying the `filter` condition
|
||||
*/
|
||||
def jobInProgress[T](filter: PartialFunction[Job[_], Option[T]]): Option[T]
|
||||
|
@ -173,6 +173,14 @@ final class JobExecutionEngine(
|
||||
result
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def stopBackgroundJobs(): Boolean =
|
||||
synchronized {
|
||||
val result = isBackgroundJobsStarted
|
||||
isBackgroundJobsStarted = false
|
||||
result
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def stop(): Unit = {
|
||||
val allJobs = runningJobsRef.get()
|
||||
|
@ -1268,4 +1268,86 @@ class RuntimeSuggestionUpdatesTest
|
||||
context.consumeOut shouldEqual List("Hello World!")
|
||||
}
|
||||
|
||||
it should "invalidate modules index on command" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
|
||||
val code =
|
||||
"""from Standard.Base import all
|
||||
|
|
||||
|main = IO.println "Hello World!"
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val mainFile = context.writeMain(code)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, code))
|
||||
)
|
||||
context.receiveNone shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val updates1 = context.receiveNIgnoreExpressionUpdates(4)
|
||||
updates1.length shouldEqual 4
|
||||
updates1 should contain allOf (
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
context.executionComplete(contextId),
|
||||
Api.Response(Api.BackgroundJobsStartedNotification())
|
||||
)
|
||||
val indexedModules = updates1.collect {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.SuggestionsDatabaseModuleUpdateNotification(moduleName, _, _, _)
|
||||
) =>
|
||||
moduleName
|
||||
}
|
||||
indexedModules should contain theSameElementsAs Seq(moduleName)
|
||||
context.consumeOut shouldEqual List("Hello World!")
|
||||
|
||||
// clear indexes
|
||||
context.send(Api.Request(requestId, Api.InvalidateModulesIndexRequest()))
|
||||
context.receiveN(1) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.InvalidateModulesIndexResponse())
|
||||
)
|
||||
|
||||
// recompute
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None, None))
|
||||
)
|
||||
val updates2 = context.receiveNIgnoreExpressionUpdates(4)
|
||||
updates2.length shouldEqual 4
|
||||
updates2 should contain allOf (
|
||||
Api.Response(requestId, Api.RecomputeContextResponse(contextId)),
|
||||
context.executionComplete(contextId),
|
||||
Api.Response(Api.BackgroundJobsStartedNotification())
|
||||
)
|
||||
val indexedModules2 = updates1.collect {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.SuggestionsDatabaseModuleUpdateNotification(moduleName, _, _, _)
|
||||
) =>
|
||||
moduleName
|
||||
}
|
||||
indexedModules2 should contain theSameElementsAs Seq(moduleName)
|
||||
context.consumeOut shouldEqual List("Hello World!")
|
||||
}
|
||||
}
|
||||
|
@ -397,7 +397,8 @@ final class SerializationManager(
|
||||
compiler.context.logSerializationManager(
|
||||
debugLogLevel,
|
||||
"Restored IR from cache for module [{0}] at stage [{1}].",
|
||||
Array[Object](module.getName, loadedCache.compilationStage())
|
||||
module.getName,
|
||||
loadedCache.compilationStage()
|
||||
)
|
||||
|
||||
if (!relinkedIrChecks.contains(false)) {
|
||||
|
@ -6,8 +6,8 @@ import org.enso.jsonrpc.Errors.InvalidParams
|
||||
|
||||
/** An actor responsible for passing parsed massages between the web and
|
||||
* a controller actor.
|
||||
* @param protocol a factory for retrieving protocol object describing supported messages and their
|
||||
* serialization modes.
|
||||
* @param protocolFactory a factory for retrieving protocol object describing
|
||||
* supported messages and their serialization modes.
|
||||
* @param controller the controller actor, handling parsed messages.
|
||||
*/
|
||||
class MessageHandler(protocolFactory: ProtocolFactory, controller: ActorRef)
|
||||
@ -32,7 +32,7 @@ class MessageHandler(protocolFactory: ProtocolFactory, controller: ActorRef)
|
||||
* response deserialization.
|
||||
* @return the connected actor behavior.
|
||||
*/
|
||||
def established(
|
||||
private def established(
|
||||
webConnection: ActorRef,
|
||||
awaitingResponses: Map[Id, Method]
|
||||
): Receive = {
|
||||
|
Loading…
Reference in New Issue
Block a user