mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 07:12:20 +03:00
parent
399fa5edfe
commit
b3badf1b80
@ -1,5 +1,10 @@
|
|||||||
# Enso Next
|
# Enso Next
|
||||||
|
|
||||||
|
## Tooling
|
||||||
|
|
||||||
|
- Implemented an HTTP endponint returning the time that the language server has
|
||||||
|
spent idle ([#1847](https://github.com/enso-org/enso/pull/1847)).
|
||||||
|
|
||||||
# Enso 0.2.13 (2021-07-09)
|
# Enso 0.2.13 (2021-07-09)
|
||||||
|
|
||||||
## Interpreter/Runtime
|
## Interpreter/Runtime
|
||||||
|
@ -45,3 +45,5 @@ The protocol messages are broken up into documents as follows:
|
|||||||
The messages and types pertaining to the project manager component.
|
The messages and types pertaining to the project manager component.
|
||||||
- [**Language Server Message Specification:**](./protocol-language-server.md)
|
- [**Language Server Message Specification:**](./protocol-language-server.md)
|
||||||
The messages and types pertaining to the language server component.
|
The messages and types pertaining to the language server component.
|
||||||
|
- [**Language Server Http Endpoints Specification**](./language-server-http-endoints.md)
|
||||||
|
Specification of the Language Server Http endpoints.
|
||||||
|
140
docs/language-server/language-server-http-endpoints.md
Normal file
140
docs/language-server/language-server-http-endpoints.md
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
layout: developer-doc
|
||||||
|
title: Language Server HTTP Endpoints
|
||||||
|
category: language-server
|
||||||
|
tags: [language-server, protocol, specification]
|
||||||
|
order: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
# HTTP Endpoints
|
||||||
|
|
||||||
|
Language server exposes a number of HTTP endpoints on the same socket as the
|
||||||
|
JSONRPC protocol.
|
||||||
|
|
||||||
|
<!-- MarkdownTOC levels="2" autolink="true" indent=" " -->
|
||||||
|
|
||||||
|
- [`/_health`](#---health-)
|
||||||
|
- [`/_health/readiness`](#---health-readiness-)
|
||||||
|
- [`/_health/liveness`](#---health-liveness-)
|
||||||
|
- [`/_idle`](#---idle-)
|
||||||
|
|
||||||
|
<!-- /MarkdownTOC -->
|
||||||
|
|
||||||
|
## `/_health`
|
||||||
|
|
||||||
|
HTTP endpoint that provides basic health checking capabilities.
|
||||||
|
|
||||||
|
### `GET | HEAD`
|
||||||
|
|
||||||
|
Returns `200 OK` when the server is started and `500 Internal Server Error`
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```text
|
||||||
|
> GET /_health HTTP/1.1
|
||||||
|
> Host: localhost:63597
|
||||||
|
> User-Agent: curl/7.77.0
|
||||||
|
> Accept: */*
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
```text
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Server: akka-http/10.2.0-RC1
|
||||||
|
< Date: Fri, 09 Jul 2021 15:16:16 GMT
|
||||||
|
< Content-Type: text/plain; charset=UTF-8
|
||||||
|
< Content-Length: 2
|
||||||
|
<
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## `/_health/readiness`
|
||||||
|
|
||||||
|
The server readiness probe.
|
||||||
|
|
||||||
|
### `GET | HEAD`
|
||||||
|
|
||||||
|
Returns `200 OK` when the server is initialized and `500 Internal Server Error`
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```text
|
||||||
|
> GET /_health/readiness HTTP/1.1
|
||||||
|
> Host: localhost:63597
|
||||||
|
> User-Agent: curl/7.77.0
|
||||||
|
> Accept: */*
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
```text
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Server: akka-http/10.2.0-RC1
|
||||||
|
< Date: Fri, 09 Jul 2021 15:30:53 GMT
|
||||||
|
< Content-Type: text/plain; charset=UTF-8
|
||||||
|
< Content-Length: 2
|
||||||
|
<
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## `/_health/liveness`
|
||||||
|
|
||||||
|
The server liveness probe.
|
||||||
|
|
||||||
|
### `GET | HEAD`
|
||||||
|
|
||||||
|
Checks if all the server subsystems are functioning and returns `200 OK` or
|
||||||
|
`500 Internal Server Error` otherwise.
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```text
|
||||||
|
> GET /_health/liveness HTTP/1.1
|
||||||
|
> Host: localhost:60339
|
||||||
|
> User-Agent: curl/7.77.0
|
||||||
|
> Accept: */*
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
```text
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Server: akka-http/10.2.0-RC1
|
||||||
|
< Date: Fri, 09 Jul 2021 15:35:43 GMT
|
||||||
|
< Content-Type: text/plain; charset=UTF-8
|
||||||
|
< Content-Length: 2
|
||||||
|
<
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## `/_idle`
|
||||||
|
|
||||||
|
The server idleness probe.
|
||||||
|
|
||||||
|
### `GET`
|
||||||
|
|
||||||
|
Return the amount of time the language server is idle.
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
```text
|
||||||
|
> GET /_idle HTTP/1.1
|
||||||
|
> Host: localhost:60339
|
||||||
|
> User-Agent: curl/7.77.0
|
||||||
|
> Accept: */*
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
```text
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Server: akka-http/10.2.0-RC1
|
||||||
|
< Date: Fri, 09 Jul 2021 15:44:51 GMT
|
||||||
|
< Content-Type: application/json
|
||||||
|
< Content-Length: 21
|
||||||
|
<
|
||||||
|
{"idle_time_sec":58}
|
||||||
|
```
|
@ -2,6 +2,7 @@ package org.enso.languageserver.boot
|
|||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.time.Clock
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import org.enso.jsonrpc.JsonRpcServer
|
import org.enso.jsonrpc.JsonRpcServer
|
||||||
@ -21,7 +22,11 @@ import org.enso.languageserver.filemanager.{
|
|||||||
}
|
}
|
||||||
import org.enso.languageserver.http.server.BinaryWebSocketServer
|
import org.enso.languageserver.http.server.BinaryWebSocketServer
|
||||||
import org.enso.languageserver.io._
|
import org.enso.languageserver.io._
|
||||||
import org.enso.languageserver.monitoring.HealthCheckEndpoint
|
import org.enso.languageserver.monitoring.{
|
||||||
|
HealthCheckEndpoint,
|
||||||
|
IdlenessEndpoint,
|
||||||
|
IdlenessMonitor
|
||||||
|
}
|
||||||
import org.enso.languageserver.protocol.binary.{
|
import org.enso.languageserver.protocol.binary.{
|
||||||
BinaryConnectionControllerFactory,
|
BinaryConnectionControllerFactory,
|
||||||
InboundMessageDecoder
|
InboundMessageDecoder
|
||||||
@ -60,6 +65,8 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
|
|||||||
logLevel
|
logLevel
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val utcClock = Clock.systemUTC()
|
||||||
|
|
||||||
val directoriesConfig = ProjectDirectoriesConfig(serverConfig.contentRootPath)
|
val directoriesConfig = ProjectDirectoriesConfig(serverConfig.contentRootPath)
|
||||||
private val contentRoot = ContentRootWithFile(
|
private val contentRoot = ContentRootWithFile(
|
||||||
ContentRoot.Project(serverConfig.contentRootUuid),
|
ContentRoot.Project(serverConfig.contentRootUuid),
|
||||||
@ -105,6 +112,9 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
|
|||||||
val versionsRepo = new SqlVersionsRepo(sqlDatabase)(system.dispatcher)
|
val versionsRepo = new SqlVersionsRepo(sqlDatabase)(system.dispatcher)
|
||||||
log.trace("Created SQL repos: [{}. {}].", suggestionsRepo, versionsRepo)
|
log.trace("Created SQL repos: [{}. {}].", suggestionsRepo, versionsRepo)
|
||||||
|
|
||||||
|
val idlenessMonitor =
|
||||||
|
system.actorOf(IdlenessMonitor.props(utcClock))
|
||||||
|
|
||||||
lazy val sessionRouter =
|
lazy val sessionRouter =
|
||||||
system.actorOf(SessionRouter.props(), "session-router")
|
system.actorOf(SessionRouter.props(), "session-router")
|
||||||
|
|
||||||
@ -272,6 +282,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
|
|||||||
stdErrController,
|
stdErrController,
|
||||||
stdInController,
|
stdInController,
|
||||||
runtimeConnector,
|
runtimeConnector,
|
||||||
|
idlenessMonitor,
|
||||||
languageServerConfig
|
languageServerConfig
|
||||||
)
|
)
|
||||||
log.trace(
|
log.trace(
|
||||||
@ -296,13 +307,16 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
|
|||||||
serverConfig.computeExecutionContext
|
serverConfig.computeExecutionContext
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val idlenessEndpoint =
|
||||||
|
new IdlenessEndpoint(idlenessMonitor)
|
||||||
|
|
||||||
val jsonRpcServer =
|
val jsonRpcServer =
|
||||||
new JsonRpcServer(
|
new JsonRpcServer(
|
||||||
JsonRpc.protocol,
|
JsonRpc.protocol,
|
||||||
jsonRpcControllerFactory,
|
jsonRpcControllerFactory,
|
||||||
JsonRpcServer
|
JsonRpcServer
|
||||||
.Config(outgoingBufferSize = 10000, lazyMessageTimeout = 10.seconds),
|
.Config(outgoingBufferSize = 10000, lazyMessageTimeout = 10.seconds),
|
||||||
List(healthCheckEndpoint)
|
List(healthCheckEndpoint, idlenessEndpoint)
|
||||||
)
|
)
|
||||||
log.trace("Created JSON RPC Server [{}].", jsonRpcServer)
|
log.trace("Created JSON RPC Server [{}].", jsonRpcServer)
|
||||||
|
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.enso.languageserver.monitoring
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.http.scaladsl.model.{
|
||||||
|
ContentTypes,
|
||||||
|
HttpEntity,
|
||||||
|
MessageEntity,
|
||||||
|
StatusCodes
|
||||||
|
}
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.http.scaladsl.server.Route
|
||||||
|
import akka.pattern.ask
|
||||||
|
import akka.util.Timeout
|
||||||
|
import com.typesafe.scalalogging.LazyLogging
|
||||||
|
import org.enso.jsonrpc._
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
|
/** HTTP endpoint that provides idleness capabilities.
|
||||||
|
*
|
||||||
|
* @param idlenessMonitor an actor monitoring the server idle time
|
||||||
|
*/
|
||||||
|
class IdlenessEndpoint(
|
||||||
|
idlenessMonitor: ActorRef
|
||||||
|
) extends Endpoint
|
||||||
|
with LazyLogging {
|
||||||
|
|
||||||
|
implicit private val timeout: Timeout = Timeout(10.seconds)
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def route: Route =
|
||||||
|
idlenessProbe
|
||||||
|
|
||||||
|
private val idlenessProbe =
|
||||||
|
path("_idle") {
|
||||||
|
get {
|
||||||
|
checkIdleness()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def checkIdleness(): Route = {
|
||||||
|
val future = idlenessMonitor ? MonitoringProtocol.GetIdleTime
|
||||||
|
|
||||||
|
onComplete(future) {
|
||||||
|
case Failure(_) =>
|
||||||
|
complete(StatusCodes.InternalServerError)
|
||||||
|
case Success(r: MonitoringProtocol.IdleTime) =>
|
||||||
|
complete(IdlenessEndpoint.toHttpEntity(r))
|
||||||
|
case Success(r) =>
|
||||||
|
logger.error("Unexpected response from idleness monitor: [{}]", r)
|
||||||
|
complete(StatusCodes.InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object IdlenessEndpoint {
|
||||||
|
|
||||||
|
private def toJsonText(t: MonitoringProtocol.IdleTime): String =
|
||||||
|
s"""{"idle_time_sec":${t.idleTimeSeconds}}"""
|
||||||
|
|
||||||
|
def toHttpEntity(t: MonitoringProtocol.IdleTime): MessageEntity =
|
||||||
|
HttpEntity(ContentTypes.`application/json`, toJsonText(t))
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.enso.languageserver.monitoring
|
||||||
|
|
||||||
|
import java.time.{Clock, Duration, Instant}
|
||||||
|
|
||||||
|
import akka.actor.{Actor, Props}
|
||||||
|
import org.enso.languageserver.util.UnhandledLogging
|
||||||
|
|
||||||
|
/** An actor that monitors the server time spent idle.
|
||||||
|
*
|
||||||
|
* @param clock the system clock
|
||||||
|
*/
|
||||||
|
class IdlenessMonitor(clock: Clock) extends Actor with UnhandledLogging {
|
||||||
|
|
||||||
|
override def receive: Receive = initialized(clock.instant())
|
||||||
|
|
||||||
|
private def initialized(lastActiveTime: Instant): Receive = {
|
||||||
|
case MonitoringProtocol.ResetIdleTime =>
|
||||||
|
context.become(initialized(clock.instant()))
|
||||||
|
|
||||||
|
case MonitoringProtocol.GetIdleTime =>
|
||||||
|
val idleTime = Duration.between(lastActiveTime, clock.instant())
|
||||||
|
sender() ! MonitoringProtocol.IdleTime(idleTime.toSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object IdlenessMonitor {
|
||||||
|
|
||||||
|
/** Creates a configuration object used to create an idleness monitor.
|
||||||
|
*
|
||||||
|
* @return a configuration object
|
||||||
|
*/
|
||||||
|
def props(clock: Clock): Props = Props(new IdlenessMonitor(clock))
|
||||||
|
|
||||||
|
}
|
@ -26,4 +26,16 @@ object MonitoringProtocol {
|
|||||||
*/
|
*/
|
||||||
case object OK extends ReadinessResponse
|
case object OK extends ReadinessResponse
|
||||||
|
|
||||||
|
/** A request to reset the idle time. */
|
||||||
|
case object ResetIdleTime
|
||||||
|
|
||||||
|
/** A request to get the server idle time. */
|
||||||
|
case object GetIdleTime
|
||||||
|
|
||||||
|
/** A response containing the idle time.
|
||||||
|
*
|
||||||
|
* @param idleTimeSeconds the idle time in seconds.
|
||||||
|
*/
|
||||||
|
case class IdleTime(idleTimeSeconds: Long)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.enso.languageserver.protocol.json
|
package org.enso.languageserver.protocol.json
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status}
|
import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status}
|
||||||
import akka.pattern.pipe
|
import akka.pattern.pipe
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
@ -15,6 +17,7 @@ import org.enso.languageserver.capability.CapabilityApi.{
|
|||||||
import org.enso.languageserver.capability.CapabilityProtocol
|
import org.enso.languageserver.capability.CapabilityProtocol
|
||||||
import org.enso.languageserver.data.Config
|
import org.enso.languageserver.data.Config
|
||||||
import org.enso.languageserver.event.{
|
import org.enso.languageserver.event.{
|
||||||
|
InitializedEvent,
|
||||||
JsonSessionInitialized,
|
JsonSessionInitialized,
|
||||||
JsonSessionTerminated
|
JsonSessionTerminated
|
||||||
}
|
}
|
||||||
@ -24,6 +27,7 @@ import org.enso.languageserver.io.InputOutputApi._
|
|||||||
import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput}
|
import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput}
|
||||||
import org.enso.languageserver.io.{InputOutputApi, InputOutputProtocol}
|
import org.enso.languageserver.io.{InputOutputApi, InputOutputProtocol}
|
||||||
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
|
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
|
||||||
|
import org.enso.languageserver.monitoring.MonitoringProtocol
|
||||||
import org.enso.languageserver.refactoring.RefactoringApi.RenameProject
|
import org.enso.languageserver.refactoring.RefactoringApi.RenameProject
|
||||||
import org.enso.languageserver.requesthandler._
|
import org.enso.languageserver.requesthandler._
|
||||||
import org.enso.languageserver.requesthandler.capability._
|
import org.enso.languageserver.requesthandler.capability._
|
||||||
@ -63,7 +67,6 @@ import org.enso.languageserver.text.TextProtocol
|
|||||||
import org.enso.languageserver.util.UnhandledLogging
|
import org.enso.languageserver.util.UnhandledLogging
|
||||||
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo
|
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo
|
||||||
|
|
||||||
import java.util.UUID
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/** An actor handling communications between a single client and the language
|
/** An actor handling communications between a single client and the language
|
||||||
@ -77,6 +80,7 @@ import scala.concurrent.duration._
|
|||||||
* @param contentRootManager manages the available content roots
|
* @param contentRootManager manages the available content roots
|
||||||
* @param contextRegistry a router that dispatches execution context requests
|
* @param contextRegistry a router that dispatches execution context requests
|
||||||
* @param suggestionsHandler a reference to the suggestions requests handler
|
* @param suggestionsHandler a reference to the suggestions requests handler
|
||||||
|
* @param idlenessMonitor a reference to the idleness monitor actor
|
||||||
* @param requestTimeout a request timeout
|
* @param requestTimeout a request timeout
|
||||||
*/
|
*/
|
||||||
class JsonConnectionController(
|
class JsonConnectionController(
|
||||||
@ -92,6 +96,7 @@ class JsonConnectionController(
|
|||||||
val stdErrController: ActorRef,
|
val stdErrController: ActorRef,
|
||||||
val stdInController: ActorRef,
|
val stdInController: ActorRef,
|
||||||
val runtimeConnector: ActorRef,
|
val runtimeConnector: ActorRef,
|
||||||
|
val idlenessMonitor: ActorRef,
|
||||||
val languageServerConfig: Config,
|
val languageServerConfig: Config,
|
||||||
requestTimeout: FiniteDuration = 10.seconds
|
requestTimeout: FiniteDuration = 10.seconds
|
||||||
) extends Actor
|
) extends Actor
|
||||||
@ -157,6 +162,9 @@ class JsonConnectionController(
|
|||||||
logger.info("RPC session initialized for client [{}].", clientId)
|
logger.info("RPC session initialized for client [{}].", clientId)
|
||||||
val session = JsonSession(clientId, self)
|
val session = JsonSession(clientId, self)
|
||||||
context.system.eventStream.publish(JsonSessionInitialized(session))
|
context.system.eventStream.publish(JsonSessionInitialized(session))
|
||||||
|
context.system.eventStream.publish(
|
||||||
|
InitializedEvent.InitializationFinished
|
||||||
|
)
|
||||||
|
|
||||||
val cancellable = context.system.scheduler.scheduleOnce(
|
val cancellable = context.system.scheduler.scheduleOnce(
|
||||||
requestTimeout,
|
requestTimeout,
|
||||||
@ -179,6 +187,7 @@ class JsonConnectionController(
|
|||||||
case Status.Failure(ex) =>
|
case Status.Failure(ex) =>
|
||||||
logger.error("Failed to initialize the resources. {}", ex.getMessage)
|
logger.error("Failed to initialize the resources. {}", ex.getMessage)
|
||||||
receiver ! ResponseError(Some(request.id), ResourcesInitializationError)
|
receiver ! ResponseError(Some(request.id), ResourcesInitializationError)
|
||||||
|
context.system.eventStream.publish(InitializedEvent.InitializationFailed)
|
||||||
context.become(connected(webActor))
|
context.become(connected(webActor))
|
||||||
|
|
||||||
case _ => stash()
|
case _ => stash()
|
||||||
@ -356,6 +365,7 @@ class JsonConnectionController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case req @ Request(method, _, _) if requestHandlers.contains(method) =>
|
case req @ Request(method, _, _) if requestHandlers.contains(method) =>
|
||||||
|
refreshIdleTime(method)
|
||||||
val handler = context.actorOf(
|
val handler = context.actorOf(
|
||||||
requestHandlers(method),
|
requestHandlers(method),
|
||||||
s"request-handler-$method-${UUID.randomUUID()}"
|
s"request-handler-$method-${UUID.randomUUID()}"
|
||||||
@ -363,6 +373,15 @@ class JsonConnectionController(
|
|||||||
handler.forward(req)
|
handler.forward(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def refreshIdleTime(method: Method): Unit = {
|
||||||
|
method match {
|
||||||
|
case InitialPing | Ping =>
|
||||||
|
// ignore
|
||||||
|
case _ =>
|
||||||
|
idlenessMonitor ! MonitoringProtocol.ResetIdleTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def createRequestHandlers(
|
private def createRequestHandlers(
|
||||||
rpcSession: JsonSession
|
rpcSession: JsonSession
|
||||||
): Map[Method, Props] = {
|
): Map[Method, Props] = {
|
||||||
@ -473,6 +492,7 @@ object JsonConnectionController {
|
|||||||
stdErrController: ActorRef,
|
stdErrController: ActorRef,
|
||||||
stdInController: ActorRef,
|
stdInController: ActorRef,
|
||||||
runtimeConnector: ActorRef,
|
runtimeConnector: ActorRef,
|
||||||
|
idlenessMonitor: ActorRef,
|
||||||
languageServerConfig: Config,
|
languageServerConfig: Config,
|
||||||
requestTimeout: FiniteDuration = 10.seconds
|
requestTimeout: FiniteDuration = 10.seconds
|
||||||
): Props =
|
): Props =
|
||||||
@ -490,6 +510,7 @@ object JsonConnectionController {
|
|||||||
stdErrController,
|
stdErrController,
|
||||||
stdInController,
|
stdInController,
|
||||||
runtimeConnector,
|
runtimeConnector,
|
||||||
|
idlenessMonitor,
|
||||||
languageServerConfig,
|
languageServerConfig,
|
||||||
requestTimeout
|
requestTimeout
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,7 @@ class JsonConnectionControllerFactory(
|
|||||||
stdErrController: ActorRef,
|
stdErrController: ActorRef,
|
||||||
stdInController: ActorRef,
|
stdInController: ActorRef,
|
||||||
runtimeConnector: ActorRef,
|
runtimeConnector: ActorRef,
|
||||||
|
idlenessMonitor: ActorRef,
|
||||||
config: Config
|
config: Config
|
||||||
)(implicit system: ActorSystem)
|
)(implicit system: ActorSystem)
|
||||||
extends ClientControllerFactory {
|
extends ClientControllerFactory {
|
||||||
@ -50,6 +51,7 @@ class JsonConnectionControllerFactory(
|
|||||||
stdErrController,
|
stdErrController,
|
||||||
stdInController,
|
stdInController,
|
||||||
runtimeConnector,
|
runtimeConnector,
|
||||||
|
idlenessMonitor,
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package org.enso.languageserver
|
||||||
|
|
||||||
|
import java.time.{Clock, Instant, ZoneId, ZoneOffset}
|
||||||
|
|
||||||
|
/** Test clock which time flow can be controlled.
|
||||||
|
*
|
||||||
|
* @param instant the initial point in time
|
||||||
|
*/
|
||||||
|
case class TestClock(var instant: Instant = Instant.EPOCH) extends Clock {
|
||||||
|
|
||||||
|
private val UTC = ZoneOffset.UTC
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getZone: ZoneId = UTC
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def withZone(zone: ZoneId): Clock =
|
||||||
|
TestClock(instant)
|
||||||
|
|
||||||
|
/** Move time forward by the specified amount of seconds.
|
||||||
|
*
|
||||||
|
* @param seconds the amount of seconds
|
||||||
|
*/
|
||||||
|
def moveTimeForward(seconds: Long): Unit = {
|
||||||
|
instant = instant.plusSeconds(seconds)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package org.enso.languageserver.monitoring
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.http.scaladsl.model.StatusCodes
|
||||||
|
import akka.http.scaladsl.server.Directives
|
||||||
|
import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest}
|
||||||
|
import org.enso.languageserver.TestClock
|
||||||
|
import org.enso.testkit.FlakySpec
|
||||||
|
import org.scalatest.flatspec.AnyFlatSpecLike
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
class IdlenessEndpointSpec
|
||||||
|
extends AnyFlatSpecLike
|
||||||
|
with Matchers
|
||||||
|
with FlakySpec
|
||||||
|
with ScalatestRouteTest
|
||||||
|
with Directives {
|
||||||
|
|
||||||
|
implicit val timeout = RouteTestTimeout(25.seconds)
|
||||||
|
|
||||||
|
"An idleness probe" should "reply with server idle time" in withEndpoint {
|
||||||
|
(_, _, endpoint) =>
|
||||||
|
Get("/_idle") ~> endpoint.route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[String] shouldEqual s"""{"idle_time_sec":0}"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "count idle time" in withEndpoint { (clock, _, endpoint) =>
|
||||||
|
Get("/_idle") ~> endpoint.route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[String] shouldEqual s"""{"idle_time_sec":0}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
val idleTimeSeconds = 1L
|
||||||
|
clock.moveTimeForward(idleTimeSeconds)
|
||||||
|
Get("/_idle") ~> endpoint.route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[String] shouldEqual s"""{"idle_time_sec":$idleTimeSeconds}"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "reset idle time" in withEndpoint { (clock, monitor, endpoint) =>
|
||||||
|
Get("/_idle") ~> endpoint.route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[String] shouldEqual s"""{"idle_time_sec":0}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
val idleTimeSeconds = 1L
|
||||||
|
clock.moveTimeForward(idleTimeSeconds)
|
||||||
|
Get("/_idle") ~> endpoint.route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[String] shouldEqual s"""{"idle_time_sec":$idleTimeSeconds}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor ! MonitoringProtocol.ResetIdleTime
|
||||||
|
Get("/_idle") ~> endpoint.route ~> check {
|
||||||
|
status shouldEqual StatusCodes.OK
|
||||||
|
responseAs[String] shouldEqual s"""{"idle_time_sec":0}"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def withEndpoint(
|
||||||
|
test: (TestClock, ActorRef, IdlenessEndpoint) => Any
|
||||||
|
): Unit = {
|
||||||
|
val clock = TestClock()
|
||||||
|
val idlenessMonitor = system.actorOf(IdlenessMonitor.props(clock))
|
||||||
|
val endpoint = new IdlenessEndpoint(idlenessMonitor)
|
||||||
|
|
||||||
|
test(clock, idlenessMonitor, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
package org.enso.languageserver.websocket.json
|
package org.enso.languageserver.websocket.json
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
import io.circe.literal._
|
import io.circe.literal._
|
||||||
import io.circe.parser.parse
|
import io.circe.parser.parse
|
||||||
@ -18,6 +21,7 @@ import org.enso.languageserver.effect.ZioExec
|
|||||||
import org.enso.languageserver.event.InitializedEvent
|
import org.enso.languageserver.event.InitializedEvent
|
||||||
import org.enso.languageserver.filemanager._
|
import org.enso.languageserver.filemanager._
|
||||||
import org.enso.languageserver.io._
|
import org.enso.languageserver.io._
|
||||||
|
import org.enso.languageserver.monitoring.IdlenessMonitor
|
||||||
import org.enso.languageserver.protocol.json.{
|
import org.enso.languageserver.protocol.json.{
|
||||||
JsonConnectionControllerFactory,
|
JsonConnectionControllerFactory,
|
||||||
JsonRpc
|
JsonRpc
|
||||||
@ -26,6 +30,7 @@ import org.enso.languageserver.refactoring.ProjectNameChangedEvent
|
|||||||
import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper}
|
import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper}
|
||||||
import org.enso.languageserver.search.SuggestionsHandler
|
import org.enso.languageserver.search.SuggestionsHandler
|
||||||
import org.enso.languageserver.session.SessionRouter
|
import org.enso.languageserver.session.SessionRouter
|
||||||
|
import org.enso.languageserver.TestClock
|
||||||
import org.enso.languageserver.text.BufferRegistry
|
import org.enso.languageserver.text.BufferRegistry
|
||||||
import org.enso.polyglot.data.TypeGraph
|
import org.enso.polyglot.data.TypeGraph
|
||||||
import org.enso.polyglot.runtime.Runtime.Api
|
import org.enso.polyglot.runtime.Runtime.Api
|
||||||
@ -34,8 +39,6 @@ import org.enso.testkit.EitherValue
|
|||||||
import org.enso.text.Sha3_224VersionCalculator
|
import org.enso.text.Sha3_224VersionCalculator
|
||||||
import org.scalatest.OptionValues
|
import org.scalatest.OptionValues
|
||||||
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.util.UUID
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@ -56,6 +59,7 @@ class BaseServerTest
|
|||||||
val config = mkConfig
|
val config = mkConfig
|
||||||
val runtimeConnectorProbe = TestProbe()
|
val runtimeConnectorProbe = TestProbe()
|
||||||
val versionCalculator = Sha3_224VersionCalculator
|
val versionCalculator = Sha3_224VersionCalculator
|
||||||
|
val clock = TestClock()
|
||||||
|
|
||||||
val typeGraph: TypeGraph = {
|
val typeGraph: TypeGraph = {
|
||||||
val graph = TypeGraph("Any")
|
val graph = TypeGraph("Any")
|
||||||
@ -150,6 +154,10 @@ class BaseServerTest
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val idlenessMonitor = system.actorOf(
|
||||||
|
IdlenessMonitor.props(clock)
|
||||||
|
)
|
||||||
|
|
||||||
val contextRegistry =
|
val contextRegistry =
|
||||||
system.actorOf(
|
system.actorOf(
|
||||||
ContextRegistry.props(
|
ContextRegistry.props(
|
||||||
@ -209,6 +217,7 @@ class BaseServerTest
|
|||||||
stdErrController,
|
stdErrController,
|
||||||
stdInController,
|
stdInController,
|
||||||
runtimeConnectorProbe.ref,
|
runtimeConnectorProbe.ref,
|
||||||
|
idlenessMonitor,
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user