diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala index 42632dd14d..aed904fe56 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala @@ -61,6 +61,7 @@ class JsonConnectionControllerFactory( projectSettingsManager = projectSettingsManager, libraryConfig = libraryConfig, languageServerConfig = config - ) + ), + s"json-connection-controller-$clientId" ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index ab534b2111..42c29214dc 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -308,7 +308,7 @@ object ContextRegistryProtocol { */ case class ExecutionDiagnostic( kind: ExecutionDiagnosticKind, - message: String, + message: Option[String], path: Option[Path], location: Option[model.Range], expressionId: Option[UUID], diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala index f17bbb8b23..414fc89b75 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala @@ -409,7 +409,7 @@ class ContextEventsListenerSpec Seq( ExecutionDiagnostic( ExecutionDiagnosticKind.Error, - message, + Some(message), None, None, None, diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 88a96578e7..ad1a9e3ae1 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -847,7 +847,7 @@ object Runtime { */ case class Diagnostic( kind: DiagnosticType, - message: String, + message: Option[String], file: Option[File], location: Option[model.Range], expressionId: Option[ExpressionId], @@ -858,7 +858,7 @@ object Runtime { override def toLogString(shouldMask: Boolean): String = "Diagnostic(" + s"kind=$kind," + - s"message=${MaskedString(message).toLogString(shouldMask)}," + + s"message=${message.map(m => MaskedString(m).toLogString(shouldMask))}," + s"file=${file.map(f => MaskedPath(f.toPath).toLogString(shouldMask))}," + s"location=$location," + s"expressionId=$expressionId," + @@ -886,7 +886,7 @@ object Runtime { ): Diagnostic = new Diagnostic( DiagnosticType.Error(), - message, + Option(message), file, location, expressionId, @@ -911,7 +911,7 @@ object Runtime { ): Diagnostic = new Diagnostic( DiagnosticType.Warning(), - message, + Option(message), file, location, expressionId, diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala index bbfff6e50c..05ea904f51 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala @@ -183,7 +183,7 @@ final class EnsureCompiledJob(protected val files: Iterable[File]) ): Api.ExecutionResult.Diagnostic = { Api.ExecutionResult.Diagnostic( kind, - diagnostic.message, + Option(diagnostic.message), Option(module.getPath).map(new File(_)), diagnostic.location .map(loc => diff --git a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonRpcServer.scala b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonRpcServer.scala index dfcbb94167..f2261969d6 100644 --- a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonRpcServer.scala +++ b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/JsonRpcServer.scala @@ -36,14 +36,11 @@ class JsonRpcServer( implicit val ec: ExecutionContext = system.dispatcher private def newUser(): Flow[Message, Message, NotUsed] = { - val clientId = UUID.randomUUID() - val clientActor = clientControllerFactory.createClientController(clientId) - val messageHandler = system.actorOf( - Props(new MessageHandler(protocol, clientActor)) + Props(new MessageHandlerSupervisor(clientControllerFactory, protocol)), + s"message-handler-supervisor-${UUID.randomUUID()}" ) - clientActor ! JsonRpcServer.WebConnect(messageHandler) val incomingMessages: Sink[Message, NotUsed] = Flow[Message] diff --git a/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandlerSupervisor.scala b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandlerSupervisor.scala new file mode 100644 index 0000000000..7db5bd951b --- /dev/null +++ b/lib/scala/json-rpc-server/src/main/scala/org/enso/jsonrpc/MessageHandlerSupervisor.scala @@ -0,0 +1,77 @@ +package org.enso.jsonrpc + +import akka.actor.{ + Actor, + ActorRef, + OneForOneStrategy, + Props, + Stash, + SupervisorStrategy +} +import com.typesafe.scalalogging.LazyLogging + +import java.util.UUID + +/** An actor responsible for supervising the [[MessageHandler]]. + * + * @param protocol a protocol supported be the server + * @param clientControllerFactory a factory used to create a client controller + */ +final class MessageHandlerSupervisor( + clientControllerFactory: ClientControllerFactory, + protocol: Protocol +) extends Actor + with LazyLogging + with Stash { + + import MessageHandlerSupervisor._ + + override def preStart(): Unit = { + self ! Initialize + } + + override def receive: Receive = uninitialized + + override val supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { + errorDecider.orElse(SupervisorStrategy.defaultDecider) + } + + /** Method defining the supervising behavior in case of errors. + * + * Child [[MessageHandler]] actor maintains a state that will be lost if + * the actor is restarted (default akka supervising behavior). Instead of + * restarting we log the error and allow it to continue handling messages. + */ + private def errorDecider: SupervisorStrategy.Decider = { + case error: Exception => + logger.warn("Resuming after error.", error) + SupervisorStrategy.Resume + } + + private def uninitialized: Receive = { + case Initialize => + val clientId = UUID.randomUUID() + val clientActor = clientControllerFactory.createClientController(clientId) + + val messageHandler = + context.actorOf( + Props(new MessageHandler(protocol, clientActor)), + s"message-handler-$clientId" + ) + clientActor ! JsonRpcServer.WebConnect(messageHandler) + context.become(initialized(messageHandler)) + unstashAll() + + case _ => + stash() + } + + private def initialized(messageHandler: ActorRef): Receive = { case message => + messageHandler.forward(message) + } +} + +object MessageHandlerSupervisor { + + case object Initialize +} diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ManagerClientControllerFactory.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ManagerClientControllerFactory.scala index aad116a9f9..987bfe8269 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ManagerClientControllerFactory.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ManagerClientControllerFactory.scala @@ -50,7 +50,7 @@ class ManagerClientControllerFactory[ loggingServiceDescriptor, timeoutConfig ), - s"jsonrpc-connection-controller-$clientId" + s"manager-client-controller-$clientId" ) }