mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 21:01:37 +03:00
Forking Language Server in the Project Manager (#1305)
This commit is contained in:
parent
a40989e7c6
commit
9e1b49d245
9
.github/workflows/scala.yml
vendored
9
.github/workflows/scala.yml
vendored
@ -133,6 +133,11 @@ jobs:
|
|||||||
# test run. It is built before starting the tests to conserve system
|
# test run. It is built before starting the tests to conserve system
|
||||||
# memory
|
# memory
|
||||||
echo "LAUNCHER_NATIVE_IMAGE_TEST_SKIP_BUILD=true" >> $GITHUB_ENV
|
echo "LAUNCHER_NATIVE_IMAGE_TEST_SKIP_BUILD=true" >> $GITHUB_ENV
|
||||||
|
- name: Build the Runner & Runtime Uberjars
|
||||||
|
run: |
|
||||||
|
sleep 1
|
||||||
|
sbt --no-colors engine-runner/assembly
|
||||||
|
|
||||||
- name: Test Enso
|
- name: Test Enso
|
||||||
run: |
|
run: |
|
||||||
sleep 1
|
sleep 1
|
||||||
@ -151,10 +156,6 @@ jobs:
|
|||||||
sbt --no-colors searcher/Benchmark/compile
|
sbt --no-colors searcher/Benchmark/compile
|
||||||
|
|
||||||
# Build Distribution
|
# Build Distribution
|
||||||
- name: Build the Runner & Runtime Uberjars
|
|
||||||
run: |
|
|
||||||
sleep 1
|
|
||||||
sbt --no-colors engine-runner/assembly
|
|
||||||
- name: Build the Project Manager Uberjar
|
- name: Build the Project Manager Uberjar
|
||||||
run: |
|
run: |
|
||||||
sleep 1
|
sleep 1
|
||||||
|
42
build.sbt
42
build.sbt
@ -609,6 +609,26 @@ lazy val `logging-service` = project
|
|||||||
)
|
)
|
||||||
.dependsOn(`akka-native`)
|
.dependsOn(`akka-native`)
|
||||||
|
|
||||||
|
ThisBuild / testOptions += Tests.Setup(_ =>
|
||||||
|
// Note [Logging Service in Tests]
|
||||||
|
sys.props("org.enso.loggingservice.test-log-level") = "2"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Note [Logging Service in Tests]
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* As migrating the runner to our new logging service has forced us to migrate
|
||||||
|
* other components that are related to it too, some tests that used to be
|
||||||
|
* configured by logback are not properly configured anymore and log a lot of
|
||||||
|
* debug information. This is a temporary fix to make sure that there are not
|
||||||
|
* too much logs in the CI - it sets the log level for all tests to be at most
|
||||||
|
* info by default. It can be overridden by particular tests if they set up a
|
||||||
|
* logging service.
|
||||||
|
*
|
||||||
|
* This is a temporary solution and it will be obsoleted once all of our
|
||||||
|
* components do a complete migration to the new logging service, which is
|
||||||
|
* planned in tasks #1144 and #1151.
|
||||||
|
*/
|
||||||
|
|
||||||
lazy val cli = project
|
lazy val cli = project
|
||||||
.in(file("lib/scala/cli"))
|
.in(file("lib/scala/cli"))
|
||||||
.configs(Test)
|
.configs(Test)
|
||||||
@ -647,15 +667,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
|
|||||||
(Compile / run / fork) := true,
|
(Compile / run / fork) := true,
|
||||||
(Test / fork) := true,
|
(Test / fork) := true,
|
||||||
(Compile / run / connectInput) := true,
|
(Compile / run / connectInput) := true,
|
||||||
javaOptions ++= {
|
libraryDependencies ++= akka ++ Seq(akkaTestkit % Test),
|
||||||
// Note [Classpath Separation]
|
|
||||||
val runtimeClasspath =
|
|
||||||
(runtime / Compile / fullClasspath).value
|
|
||||||
.map(_.data)
|
|
||||||
.mkString(File.pathSeparator)
|
|
||||||
Seq(s"-Dtruffle.class.path.append=$runtimeClasspath")
|
|
||||||
},
|
|
||||||
libraryDependencies ++= akka,
|
|
||||||
libraryDependencies ++= circe,
|
libraryDependencies ++= circe,
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.typesafe" % "config" % typesafeConfigVersion,
|
"com.typesafe" % "config" % typesafeConfigVersion,
|
||||||
@ -665,6 +677,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
|
|||||||
"dev.zio" %% "zio-interop-cats" % zioInteropCatsVersion,
|
"dev.zio" %% "zio-interop-cats" % zioInteropCatsVersion,
|
||||||
"commons-cli" % "commons-cli" % commonsCliVersion,
|
"commons-cli" % "commons-cli" % commonsCliVersion,
|
||||||
"commons-io" % "commons-io" % commonsIoVersion,
|
"commons-io" % "commons-io" % commonsIoVersion,
|
||||||
|
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
|
||||||
"com.beachape" %% "enumeratum-circe" % enumeratumCirceVersion,
|
"com.beachape" %% "enumeratum-circe" % enumeratumCirceVersion,
|
||||||
"com.miguno.akka" %% "akka-mock-scheduler" % akkaMockSchedulerVersion % Test,
|
"com.miguno.akka" %% "akka-mock-scheduler" % akkaMockSchedulerVersion % Test,
|
||||||
"org.mockito" %% "mockito-scala" % mockitoScalaVersion % Test
|
"org.mockito" %% "mockito-scala" % mockitoScalaVersion % Test
|
||||||
@ -697,13 +710,12 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
assembly := assembly
|
assembly := assembly.dependsOn(`engine-runner` / assembly).value,
|
||||||
.dependsOn(runtime / assembly)
|
(Test / test) := (Test / test).dependsOn(`engine-runner` / assembly).value
|
||||||
.value
|
|
||||||
)
|
)
|
||||||
.dependsOn(`version-output`)
|
.dependsOn(`version-output`)
|
||||||
.dependsOn(pkg)
|
.dependsOn(pkg)
|
||||||
.dependsOn(`language-server`)
|
.dependsOn(`polyglot-api`)
|
||||||
.dependsOn(`runtime-version-manager`)
|
.dependsOn(`runtime-version-manager`)
|
||||||
.dependsOn(`json-rpc-server`)
|
.dependsOn(`json-rpc-server`)
|
||||||
.dependsOn(`json-rpc-server-test` % Test)
|
.dependsOn(`json-rpc-server-test` % Test)
|
||||||
@ -867,8 +879,7 @@ lazy val `polyglot-api` = project
|
|||||||
|
|
||||||
lazy val `language-server` = (project in file("engine/language-server"))
|
lazy val `language-server` = (project in file("engine/language-server"))
|
||||||
.settings(
|
.settings(
|
||||||
libraryDependencies ++= akka ++ akkaTest ++ circe ++ Seq(
|
libraryDependencies ++= akka ++ circe ++ Seq(
|
||||||
"ch.qos.logback" % "logback-classic" % logbackClassicVersion,
|
|
||||||
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
|
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
|
||||||
"io.circe" %% "circe-generic-extras" % circeGenericExtrasVersion,
|
"io.circe" %% "circe-generic-extras" % circeGenericExtrasVersion,
|
||||||
"io.circe" %% "circe-literal" % circeVersion,
|
"io.circe" %% "circe-literal" % circeVersion,
|
||||||
@ -902,6 +913,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
|
|||||||
.dependsOn(`text-buffer`)
|
.dependsOn(`text-buffer`)
|
||||||
.dependsOn(`searcher`)
|
.dependsOn(`searcher`)
|
||||||
.dependsOn(testkit % Test)
|
.dependsOn(testkit % Test)
|
||||||
|
.dependsOn(`logging-service`)
|
||||||
|
|
||||||
lazy val ast = (project in file("lib/scala/ast"))
|
lazy val ast = (project in file("lib/scala/ast"))
|
||||||
.settings(
|
.settings(
|
||||||
|
@ -261,11 +261,6 @@ The license file can be found at `licenses/APACHE2.0`.
|
|||||||
Copyright notices related to this dependency can be found in the directory `com.google.auto.service.auto-service-annotations-1.0-rc7`.
|
Copyright notices related to this dependency can be found in the directory `com.google.auto.service.auto-service-annotations-1.0-rc7`.
|
||||||
|
|
||||||
|
|
||||||
'logback-classic', licensed under the GNU Lesser General Public License, is distributed with the engine.
|
|
||||||
The license information can be found along with the copyright notices.
|
|
||||||
Copyright notices related to this dependency can be found in the directory `ch.qos.logback.logback-classic-1.2.3`.
|
|
||||||
|
|
||||||
|
|
||||||
'scala-collection-compat_2.13', licensed under the Apache-2.0, is distributed with the engine.
|
'scala-collection-compat_2.13', licensed under the Apache-2.0, is distributed with the engine.
|
||||||
The license file can be found at `licenses/APACHE2.0`.
|
The license file can be found at `licenses/APACHE2.0`.
|
||||||
Copyright notices related to this dependency can be found in the directory `org.scala-lang.modules.scala-collection-compat_2.13-2.0.0`.
|
Copyright notices related to this dependency can be found in the directory `org.scala-lang.modules.scala-collection-compat_2.13-2.0.0`.
|
||||||
@ -466,11 +461,6 @@ The license information can be found along with the copyright notices.
|
|||||||
Copyright notices related to this dependency can be found in the directory `com.beachape.enumeratum_2.13-1.6.1`.
|
Copyright notices related to this dependency can be found in the directory `com.beachape.enumeratum_2.13-1.6.1`.
|
||||||
|
|
||||||
|
|
||||||
'logback-core', licensed under the GNU Lesser General Public License, is distributed with the engine.
|
|
||||||
The license information can be found along with the copyright notices.
|
|
||||||
Copyright notices related to this dependency can be found in the directory `ch.qos.logback.logback-core-1.2.3`.
|
|
||||||
|
|
||||||
|
|
||||||
'pureconfig_2.13', licensed under the Mozilla Public License, version 2.0, is distributed with the engine.
|
'pureconfig_2.13', licensed under the Mozilla Public License, version 2.0, is distributed with the engine.
|
||||||
The license information can be found along with the copyright notices.
|
The license information can be found along with the copyright notices.
|
||||||
Copyright notices related to this dependency can be found in the directory `com.github.pureconfig.pureconfig_2.13-0.13.0`.
|
Copyright notices related to this dependency can be found in the directory `com.github.pureconfig.pureconfig_2.13-0.13.0`.
|
||||||
|
@ -1,259 +0,0 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<!-- saved from url=(0042)https://www.eclipse.org/legal/epl-v10.html -->
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"><link type="text/css" rel="stylesheet" id="dark-mode-general-link"><link type="text/css" rel="stylesheet" id="dark-mode-custom-link"><style type="text/css" id="dark-mode-custom-style"></style><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
|
||||||
|
|
||||||
<title>Eclipse Public License - Version 1.0</title>
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
size: 8.5in 11.0in;
|
|
||||||
margin: 0.25in 0.5in 0.25in 0.5in;
|
|
||||||
tab-interval: 0.5in;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
p.list {
|
|
||||||
margin-left: 0.5in;
|
|
||||||
margin-top: 0.05em;
|
|
||||||
margin-bottom: 0.05em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body lang="EN-US">
|
|
||||||
|
|
||||||
<h2>Eclipse Public License - v 1.0</h2>
|
|
||||||
|
|
||||||
<p>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
|
|
||||||
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
|
|
||||||
DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
|
|
||||||
AGREEMENT.</p>
|
|
||||||
|
|
||||||
<p><b>1. DEFINITIONS</b></p>
|
|
||||||
|
|
||||||
<p>"Contribution" means:</p>
|
|
||||||
|
|
||||||
<p class="list">a) in the case of the initial Contributor, the initial
|
|
||||||
code and documentation distributed under this Agreement, and</p>
|
|
||||||
<p class="list">b) in the case of each subsequent Contributor:</p>
|
|
||||||
<p class="list">i) changes to the Program, and</p>
|
|
||||||
<p class="list">ii) additions to the Program;</p>
|
|
||||||
<p class="list">where such changes and/or additions to the Program
|
|
||||||
originate from and are distributed by that particular Contributor. A
|
|
||||||
Contribution 'originates' from a Contributor if it was added to the
|
|
||||||
Program by such Contributor itself or anyone acting on such
|
|
||||||
Contributor's behalf. Contributions do not include additions to the
|
|
||||||
Program which: (i) are separate modules of software distributed in
|
|
||||||
conjunction with the Program under their own license agreement, and (ii)
|
|
||||||
are not derivative works of the Program.</p>
|
|
||||||
|
|
||||||
<p>"Contributor" means any person or entity that distributes
|
|
||||||
the Program.</p>
|
|
||||||
|
|
||||||
<p>"Licensed Patents" mean patent claims licensable by a
|
|
||||||
Contributor which are necessarily infringed by the use or sale of its
|
|
||||||
Contribution alone or when combined with the Program.</p>
|
|
||||||
|
|
||||||
<p>"Program" means the Contributions distributed in accordance
|
|
||||||
with this Agreement.</p>
|
|
||||||
|
|
||||||
<p>"Recipient" means anyone who receives the Program under
|
|
||||||
this Agreement, including all Contributors.</p>
|
|
||||||
|
|
||||||
<p><b>2. GRANT OF RIGHTS</b></p>
|
|
||||||
|
|
||||||
<p class="list">a) Subject to the terms of this Agreement, each
|
|
||||||
Contributor hereby grants Recipient a non-exclusive, worldwide,
|
|
||||||
royalty-free copyright license to reproduce, prepare derivative works
|
|
||||||
of, publicly display, publicly perform, distribute and sublicense the
|
|
||||||
Contribution of such Contributor, if any, and such derivative works, in
|
|
||||||
source code and object code form.</p>
|
|
||||||
|
|
||||||
<p class="list">b) Subject to the terms of this Agreement, each
|
|
||||||
Contributor hereby grants Recipient a non-exclusive, worldwide,
|
|
||||||
royalty-free patent license under Licensed Patents to make, use, sell,
|
|
||||||
offer to sell, import and otherwise transfer the Contribution of such
|
|
||||||
Contributor, if any, in source code and object code form. This patent
|
|
||||||
license shall apply to the combination of the Contribution and the
|
|
||||||
Program if, at the time the Contribution is added by the Contributor,
|
|
||||||
such addition of the Contribution causes such combination to be covered
|
|
||||||
by the Licensed Patents. The patent license shall not apply to any other
|
|
||||||
combinations which include the Contribution. No hardware per se is
|
|
||||||
licensed hereunder.</p>
|
|
||||||
|
|
||||||
<p class="list">c) Recipient understands that although each Contributor
|
|
||||||
grants the licenses to its Contributions set forth herein, no assurances
|
|
||||||
are provided by any Contributor that the Program does not infringe the
|
|
||||||
patent or other intellectual property rights of any other entity. Each
|
|
||||||
Contributor disclaims any liability to Recipient for claims brought by
|
|
||||||
any other entity based on infringement of intellectual property rights
|
|
||||||
or otherwise. As a condition to exercising the rights and licenses
|
|
||||||
granted hereunder, each Recipient hereby assumes sole responsibility to
|
|
||||||
secure any other intellectual property rights needed, if any. For
|
|
||||||
example, if a third party patent license is required to allow Recipient
|
|
||||||
to distribute the Program, it is Recipient's responsibility to acquire
|
|
||||||
that license before distributing the Program.</p>
|
|
||||||
|
|
||||||
<p class="list">d) Each Contributor represents that to its knowledge it
|
|
||||||
has sufficient copyright rights in its Contribution, if any, to grant
|
|
||||||
the copyright license set forth in this Agreement.</p>
|
|
||||||
|
|
||||||
<p><b>3. REQUIREMENTS</b></p>
|
|
||||||
|
|
||||||
<p>A Contributor may choose to distribute the Program in object code
|
|
||||||
form under its own license agreement, provided that:</p>
|
|
||||||
|
|
||||||
<p class="list">a) it complies with the terms and conditions of this
|
|
||||||
Agreement; and</p>
|
|
||||||
|
|
||||||
<p class="list">b) its license agreement:</p>
|
|
||||||
|
|
||||||
<p class="list">i) effectively disclaims on behalf of all Contributors
|
|
||||||
all warranties and conditions, express and implied, including warranties
|
|
||||||
or conditions of title and non-infringement, and implied warranties or
|
|
||||||
conditions of merchantability and fitness for a particular purpose;</p>
|
|
||||||
|
|
||||||
<p class="list">ii) effectively excludes on behalf of all Contributors
|
|
||||||
all liability for damages, including direct, indirect, special,
|
|
||||||
incidental and consequential damages, such as lost profits;</p>
|
|
||||||
|
|
||||||
<p class="list">iii) states that any provisions which differ from this
|
|
||||||
Agreement are offered by that Contributor alone and not by any other
|
|
||||||
party; and</p>
|
|
||||||
|
|
||||||
<p class="list">iv) states that source code for the Program is available
|
|
||||||
from such Contributor, and informs licensees how to obtain it in a
|
|
||||||
reasonable manner on or through a medium customarily used for software
|
|
||||||
exchange.</p>
|
|
||||||
|
|
||||||
<p>When the Program is made available in source code form:</p>
|
|
||||||
|
|
||||||
<p class="list">a) it must be made available under this Agreement; and</p>
|
|
||||||
|
|
||||||
<p class="list">b) a copy of this Agreement must be included with each
|
|
||||||
copy of the Program.</p>
|
|
||||||
|
|
||||||
<p>Contributors may not remove or alter any copyright notices contained
|
|
||||||
within the Program.</p>
|
|
||||||
|
|
||||||
<p>Each Contributor must identify itself as the originator of its
|
|
||||||
Contribution, if any, in a manner that reasonably allows subsequent
|
|
||||||
Recipients to identify the originator of the Contribution.</p>
|
|
||||||
|
|
||||||
<p><b>4. COMMERCIAL DISTRIBUTION</b></p>
|
|
||||||
|
|
||||||
<p>Commercial distributors of software may accept certain
|
|
||||||
responsibilities with respect to end users, business partners and the
|
|
||||||
like. While this license is intended to facilitate the commercial use of
|
|
||||||
the Program, the Contributor who includes the Program in a commercial
|
|
||||||
product offering should do so in a manner which does not create
|
|
||||||
potential liability for other Contributors. Therefore, if a Contributor
|
|
||||||
includes the Program in a commercial product offering, such Contributor
|
|
||||||
("Commercial Contributor") hereby agrees to defend and
|
|
||||||
indemnify every other Contributor ("Indemnified Contributor")
|
|
||||||
against any losses, damages and costs (collectively "Losses")
|
|
||||||
arising from claims, lawsuits and other legal actions brought by a third
|
|
||||||
party against the Indemnified Contributor to the extent caused by the
|
|
||||||
acts or omissions of such Commercial Contributor in connection with its
|
|
||||||
distribution of the Program in a commercial product offering. The
|
|
||||||
obligations in this section do not apply to any claims or Losses
|
|
||||||
relating to any actual or alleged intellectual property infringement. In
|
|
||||||
order to qualify, an Indemnified Contributor must: a) promptly notify
|
|
||||||
the Commercial Contributor in writing of such claim, and b) allow the
|
|
||||||
Commercial Contributor to control, and cooperate with the Commercial
|
|
||||||
Contributor in, the defense and any related settlement negotiations. The
|
|
||||||
Indemnified Contributor may participate in any such claim at its own
|
|
||||||
expense.</p>
|
|
||||||
|
|
||||||
<p>For example, a Contributor might include the Program in a commercial
|
|
||||||
product offering, Product X. That Contributor is then a Commercial
|
|
||||||
Contributor. If that Commercial Contributor then makes performance
|
|
||||||
claims, or offers warranties related to Product X, those performance
|
|
||||||
claims and warranties are such Commercial Contributor's responsibility
|
|
||||||
alone. Under this section, the Commercial Contributor would have to
|
|
||||||
defend claims against the other Contributors related to those
|
|
||||||
performance claims and warranties, and if a court requires any other
|
|
||||||
Contributor to pay any damages as a result, the Commercial Contributor
|
|
||||||
must pay those damages.</p>
|
|
||||||
|
|
||||||
<p><b>5. NO WARRANTY</b></p>
|
|
||||||
|
|
||||||
<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
|
|
||||||
PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
|
||||||
OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
|
|
||||||
ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
|
|
||||||
OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
|
|
||||||
responsible for determining the appropriateness of using and
|
|
||||||
distributing the Program and assumes all risks associated with its
|
|
||||||
exercise of rights under this Agreement , including but not limited to
|
|
||||||
the risks and costs of program errors, compliance with applicable laws,
|
|
||||||
damage to or loss of data, programs or equipment, and unavailability or
|
|
||||||
interruption of operations.</p>
|
|
||||||
|
|
||||||
<p><b>6. DISCLAIMER OF LIABILITY</b></p>
|
|
||||||
|
|
||||||
<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
|
|
||||||
NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
|
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
|
|
||||||
WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
|
|
||||||
DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
|
|
||||||
HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>
|
|
||||||
|
|
||||||
<p><b>7. GENERAL</b></p>
|
|
||||||
|
|
||||||
<p>If any provision of this Agreement is invalid or unenforceable under
|
|
||||||
applicable law, it shall not affect the validity or enforceability of
|
|
||||||
the remainder of the terms of this Agreement, and without further action
|
|
||||||
by the parties hereto, such provision shall be reformed to the minimum
|
|
||||||
extent necessary to make such provision valid and enforceable.</p>
|
|
||||||
|
|
||||||
<p>If Recipient institutes patent litigation against any entity
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that the
|
|
||||||
Program itself (excluding combinations of the Program with other
|
|
||||||
software or hardware) infringes such Recipient's patent(s), then such
|
|
||||||
Recipient's rights granted under Section 2(b) shall terminate as of the
|
|
||||||
date such litigation is filed.</p>
|
|
||||||
|
|
||||||
<p>All Recipient's rights under this Agreement shall terminate if it
|
|
||||||
fails to comply with any of the material terms or conditions of this
|
|
||||||
Agreement and does not cure such failure in a reasonable period of time
|
|
||||||
after becoming aware of such noncompliance. If all Recipient's rights
|
|
||||||
under this Agreement terminate, Recipient agrees to cease use and
|
|
||||||
distribution of the Program as soon as reasonably practicable. However,
|
|
||||||
Recipient's obligations under this Agreement and any licenses granted by
|
|
||||||
Recipient relating to the Program shall continue and survive.</p>
|
|
||||||
|
|
||||||
<p>Everyone is permitted to copy and distribute copies of this
|
|
||||||
Agreement, but in order to avoid inconsistency the Agreement is
|
|
||||||
copyrighted and may only be modified in the following manner. The
|
|
||||||
Agreement Steward reserves the right to publish new versions (including
|
|
||||||
revisions) of this Agreement from time to time. No one other than the
|
|
||||||
Agreement Steward has the right to modify this Agreement. The Eclipse
|
|
||||||
Foundation is the initial Agreement Steward. The Eclipse Foundation may
|
|
||||||
assign the responsibility to serve as the Agreement Steward to a
|
|
||||||
suitable separate entity. Each new version of the Agreement will be
|
|
||||||
given a distinguishing version number. The Program (including
|
|
||||||
Contributions) may always be distributed subject to the version of the
|
|
||||||
Agreement under which it was received. In addition, after a new version
|
|
||||||
of the Agreement is published, Contributor may elect to distribute the
|
|
||||||
Program (including its Contributions) under the new version. Except as
|
|
||||||
expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
|
|
||||||
rights or licenses to the intellectual property of any Contributor under
|
|
||||||
this Agreement, whether expressly, by implication, estoppel or
|
|
||||||
otherwise. All rights in the Program not expressly granted under this
|
|
||||||
Agreement are reserved.</p>
|
|
||||||
|
|
||||||
<p>This Agreement is governed by the laws of the State of New York and
|
|
||||||
the intellectual property laws of the United States of America. No party
|
|
||||||
to this Agreement will bring a legal action under this Agreement more
|
|
||||||
than one year after the cause of action arose. Each party waives its
|
|
||||||
rights to a jury trial in any resulting litigation.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body></html>
|
|
@ -1,16 +0,0 @@
|
|||||||
Logback: the reliable, generic, fast and flexible logging framework.
|
|
||||||
Copyright (C) 1999-2017, QOS.ch. All rights reserved.
|
|
||||||
|
|
||||||
This program and the accompanying materials are dual-licensed under
|
|
||||||
either the terms of the Eclipse Public License v1.0 as published by
|
|
||||||
the Eclipse Foundation
|
|
||||||
|
|
||||||
or (per the licensee's choosing)
|
|
||||||
|
|
||||||
under the terms of the GNU Lesser General Public License version 2.1
|
|
||||||
as published by the Free Software Foundation.
|
|
||||||
|
|
||||||
|
|
||||||
------------------
|
|
||||||
|
|
||||||
We have chosen EPL in this project.
|
|
@ -1 +0,0 @@
|
|||||||
Please see ch.qos.logback.logback-classic-1.2 for notices related to logback.
|
|
@ -116,6 +116,12 @@ The review can be performed manually by modifying the settings inside of the
|
|||||||
|
|
||||||
#### Review Process
|
#### Review Process
|
||||||
|
|
||||||
|
> The updates performed using the web script are remembered locally, so they
|
||||||
|
> **will not show up after the refresh**. If you ever need to open the edit mode
|
||||||
|
> after closing its window, you should re-generate the report using
|
||||||
|
> `enso/gatherLicenses` or just open it using `enso/openLegalReviewReport` which
|
||||||
|
> will refresh it automatically.
|
||||||
|
|
||||||
1. Open the review in edit mode using the helper script.
|
1. Open the review in edit mode using the helper script.
|
||||||
- You can type `enso / openLegalReviewReport` if you have `npm` in your PATH
|
- You can type `enso / openLegalReviewReport` if you have `npm` in your PATH
|
||||||
as visible from SBT.
|
as visible from SBT.
|
||||||
@ -192,12 +198,6 @@ The review can be performed manually by modifying the settings inside of the
|
|||||||
- Ensure that there are no more warnings, and if there are any go back to fix
|
- Ensure that there are no more warnings, and if there are any go back to fix
|
||||||
the issues.
|
the issues.
|
||||||
|
|
||||||
The updates performed using the web script are remembered locally, so they will
|
|
||||||
not show up after the refresh. If you ever need to open the edit mode after
|
|
||||||
closing its window, you should re-generate the report using
|
|
||||||
`enso/gatherLicenses` or just open it using `enso/openLegalReviewReport` which
|
|
||||||
will refresh it automatically.
|
|
||||||
|
|
||||||
#### Additional Manual Considerations
|
#### Additional Manual Considerations
|
||||||
|
|
||||||
The Scala Library notice contains the following mention:
|
The Scala Library notice contains the following mention:
|
||||||
|
@ -65,6 +65,8 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`executionContext/canModify`](#executioncontextcanmodify)
|
- [`executionContext/canModify`](#executioncontextcanmodify)
|
||||||
- [`executionContext/receivesUpdates`](#executioncontextreceivesupdates)
|
- [`executionContext/receivesUpdates`](#executioncontextreceivesupdates)
|
||||||
- [`search/receivesSuggestionsDatabaseUpdates`](#searchreceivessuggestionsdatabaseupdates)
|
- [`search/receivesSuggestionsDatabaseUpdates`](#searchreceivessuggestionsdatabaseupdates)
|
||||||
|
- [Enables](#enables-4)
|
||||||
|
- [Disables](#disables-4)
|
||||||
- [File Management Operations](#file-management-operations)
|
- [File Management Operations](#file-management-operations)
|
||||||
- [`file/write`](#filewrite)
|
- [`file/write`](#filewrite)
|
||||||
- [`file/read`](#fileread)
|
- [`file/read`](#fileread)
|
||||||
@ -94,6 +96,9 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`workspace/redo`](#workspaceredo)
|
- [`workspace/redo`](#workspaceredo)
|
||||||
- [Monitoring](#monitoring)
|
- [Monitoring](#monitoring)
|
||||||
- [`heartbeat/ping`](#heartbeatping)
|
- [`heartbeat/ping`](#heartbeatping)
|
||||||
|
- [`heartbeat/init`](#heartbeatinit)
|
||||||
|
- [Refactoring](#refactoring)
|
||||||
|
- [`refactoring/renameProject`](#refactoringrenameproject)
|
||||||
- [Execution Management Operations](#execution-management-operations)
|
- [Execution Management Operations](#execution-management-operations)
|
||||||
- [Execution Management Example](#execution-management-example)
|
- [Execution Management Example](#execution-management-example)
|
||||||
- [Create Execution Context](#create-execution-context)
|
- [Create Execution Context](#create-execution-context)
|
||||||
@ -113,9 +118,9 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`executionContext/modifyVisualisation`](#executioncontextmodifyvisualisation)
|
- [`executionContext/modifyVisualisation`](#executioncontextmodifyvisualisation)
|
||||||
- [`executionContext/visualisationUpdate`](#executioncontextvisualisationupdate)
|
- [`executionContext/visualisationUpdate`](#executioncontextvisualisationupdate)
|
||||||
- [Search Operations](#search-operations)
|
- [Search Operations](#search-operations)
|
||||||
- [Suggestions Database Example](#suggestionsdatabaseexample)
|
- [Suggestions Database Example](#suggestions-database-example)
|
||||||
- [`search/getSuggestionsDatabase`](#searchgetsuggestionsdatabase)
|
- [`search/getSuggestionsDatabase`](#searchgetsuggestionsdatabase)
|
||||||
- [`search/invalidateSuggestionsDatabase`](#invalidatesuggestionsdatabase)
|
- [`search/invalidateSuggestionsDatabase`](#searchinvalidatesuggestionsdatabase)
|
||||||
- [`search/getSuggestionsDatabaseVersion`](#searchgetsuggestionsdatabaseversion)
|
- [`search/getSuggestionsDatabaseVersion`](#searchgetsuggestionsdatabaseversion)
|
||||||
- [`search/suggestionsDatabaseUpdate`](#searchsuggestionsdatabaseupdate)
|
- [`search/suggestionsDatabaseUpdate`](#searchsuggestionsdatabaseupdate)
|
||||||
- [`search/completion`](#searchcompletion)
|
- [`search/completion`](#searchcompletion)
|
||||||
@ -124,17 +129,17 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`io/redirectStandardOutput`](#ioredirectstdardoutput)
|
- [`io/redirectStandardOutput`](#ioredirectstdardoutput)
|
||||||
- [`io/suppressStandardOutput`](#iosuppressstdardoutput)
|
- [`io/suppressStandardOutput`](#iosuppressstdardoutput)
|
||||||
- [`io/standardOutputAppended`](#iostandardoutputappended)
|
- [`io/standardOutputAppended`](#iostandardoutputappended)
|
||||||
- [`io/redirectStandardError`](#ioredirectstdarderror)
|
- [`io/redirectStandardError`](#ioredirectstandarderror)
|
||||||
- [`io/suppressStandardError`](#iosuppressstdarderror)
|
- [`io/suppressStandardError`](#iosuppressstandarderror)
|
||||||
- [`io/standardErrorAppended`](#iostandarderrorappended)
|
- [`io/standardErrorAppended`](#iostandarderrorappended)
|
||||||
- [`io/feedStandardInput`](#iofeedstandardinput)
|
- [`io/feedStandardInput`](#iofeedstandardinput)
|
||||||
- [`io/waitingForStandardInput`](#iowaitingforstandardinput)
|
- [`io/waitingForStandardInput`](#iowaitingforstandardinput)
|
||||||
- [Errors](#errors)
|
- [Errors](#errors-57)
|
||||||
- [`AccessDeniedError`](#accessdeniederror)
|
- [`AccessDeniedError`](#accessdeniederror)
|
||||||
- [`FileSystemError`](#filesystemerror)
|
- [`FileSystemError`](#filesystemerror)
|
||||||
- [`ContentRootNotFoundError`](#contentrootnotfounderror)
|
- [`ContentRootNotFoundError`](#contentrootnotfounderror)
|
||||||
- [`FileNotFound`](#filenotfound)
|
- [`FileNotFound`](#filenotfound)
|
||||||
- [`FileExists`](#fileexists-1)
|
- [`FileExists`](#fileexists)
|
||||||
- [`OperationTimeoutError`](#operationtimeouterror)
|
- [`OperationTimeoutError`](#operationtimeouterror)
|
||||||
- [`NotDirectory`](#notdirectory)
|
- [`NotDirectory`](#notdirectory)
|
||||||
- [`StackItemNotFoundError`](#stackitemnotfounderror)
|
- [`StackItemNotFoundError`](#stackitemnotfounderror)
|
||||||
@ -145,7 +150,6 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`VisualisationNotFoundError`](#visualisationnotfounderror)
|
- [`VisualisationNotFoundError`](#visualisationnotfounderror)
|
||||||
- [`VisualisationExpressionError`](#visualisationexpressionerror)
|
- [`VisualisationExpressionError`](#visualisationexpressionerror)
|
||||||
- [`VisualisationEvaluationError`](#visualisationevaluationerror)
|
- [`VisualisationEvaluationError`](#visualisationevaluationerror)
|
||||||
- [`ExecutionFailedError`](#executionfailederror)
|
|
||||||
- [`FileNotOpenedError`](#filenotopenederror)
|
- [`FileNotOpenedError`](#filenotopenederror)
|
||||||
- [`TextEditValidationError`](#texteditvalidationerror)
|
- [`TextEditValidationError`](#texteditvalidationerror)
|
||||||
- [`InvalidVersionError`](#invalidversionerror)
|
- [`InvalidVersionError`](#invalidversionerror)
|
||||||
@ -154,6 +158,8 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`SessionNotInitialisedError`](#sessionnotinitialisederror)
|
- [`SessionNotInitialisedError`](#sessionnotinitialisederror)
|
||||||
- [`SessionAlreadyInitialisedError`](#sessionalreadyinitialisederror)
|
- [`SessionAlreadyInitialisedError`](#sessionalreadyinitialisederror)
|
||||||
- [`SuggestionsDatabaseError`](#suggestionsdatabaseerror)
|
- [`SuggestionsDatabaseError`](#suggestionsdatabaseerror)
|
||||||
|
- [`ProjectNotFoundError`](#projectnotfounderror)
|
||||||
|
- [`ModuleNameNotResolvedError`](#modulenamenotresolvederror)
|
||||||
|
|
||||||
<!-- /MarkdownTOC -->
|
<!-- /MarkdownTOC -->
|
||||||
|
|
||||||
@ -2085,6 +2091,33 @@ null;
|
|||||||
|
|
||||||
None
|
None
|
||||||
|
|
||||||
|
### `heartbeat/init`
|
||||||
|
|
||||||
|
This request is sent from the bootloader to check if the started language server
|
||||||
|
instance has finished initialization. A reply should only be sent when the main
|
||||||
|
module has been fully initialized.
|
||||||
|
|
||||||
|
- **Type:** Request
|
||||||
|
- **Direction:** Supervisor -> Server
|
||||||
|
- **Connection:** Protocol
|
||||||
|
- **Visibility:** Private
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
null;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
null;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Errors
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
## Refactoring
|
## Refactoring
|
||||||
|
|
||||||
The language server also provides refactoring operations to restructure an
|
The language server also provides refactoring operations to restructure an
|
||||||
|
@ -65,6 +65,7 @@ transport formats, please look [here](./protocol-architecture.md).
|
|||||||
- [`ProjectCloseError`](#projectcloseerror)
|
- [`ProjectCloseError`](#projectcloseerror)
|
||||||
- [`LanguageServerError`](#languageservererror)
|
- [`LanguageServerError`](#languageservererror)
|
||||||
- [`GlobalConfigurationAccessError`](#globalconfigurationaccesserror)
|
- [`GlobalConfigurationAccessError`](#globalconfigurationaccesserror)
|
||||||
|
- [`ProjectCreateError`](#projectcreateerror)
|
||||||
- [`LoggingServiceUnavailable`](#loggingserviceunavailable)
|
- [`LoggingServiceUnavailable`](#loggingserviceunavailable)
|
||||||
|
|
||||||
<!-- /MarkdownTOC -->
|
<!-- /MarkdownTOC -->
|
||||||
@ -143,8 +144,8 @@ operation also includes spawning an instance of the language server open on the
|
|||||||
specified project.
|
specified project.
|
||||||
|
|
||||||
To open a project, an engine version that is specified in project settings needs
|
To open a project, an engine version that is specified in project settings needs
|
||||||
to be installed. If `missingComponentAction` is set to `install` or
|
to be installed. If `missingComponentAction` is set to `Install` or
|
||||||
`force-install-broken`, this action will install any missing components,
|
`ForceInstallBroken`, this action will install any missing components,
|
||||||
otherwise, an error will be reported if a component is missing. A typical usage
|
otherwise, an error will be reported if a component is missing. A typical usage
|
||||||
scenario may consist of first trying to open the project without installing
|
scenario may consist of first trying to open the project without installing
|
||||||
missing components. If that fails with the `MissingComponentError`, the client
|
missing components. If that fails with the `MissingComponentError`, the client
|
||||||
@ -165,7 +166,7 @@ interface ProjectOpenRequest {
|
|||||||
/**
|
/**
|
||||||
* Specifies how to handle missing components.
|
* Specifies how to handle missing components.
|
||||||
*
|
*
|
||||||
* If not provided, defaults to `fail`.
|
* If not provided, defaults to `Fail`.
|
||||||
*/
|
*/
|
||||||
missingComponentAction?: MissingComponentAction;
|
missingComponentAction?: MissingComponentAction;
|
||||||
}
|
}
|
||||||
@ -303,7 +304,7 @@ interface ProjectCreateRequest {
|
|||||||
/**
|
/**
|
||||||
* Specifies how to handle missing components.
|
* Specifies how to handle missing components.
|
||||||
*
|
*
|
||||||
* If not provided, defaults to `fail`.
|
* If not provided, defaults to `Fail`.
|
||||||
*/
|
*/
|
||||||
missingComponentAction?: MissingComponentAction;
|
missingComponentAction?: MissingComponentAction;
|
||||||
}
|
}
|
||||||
@ -1045,13 +1046,24 @@ Signals that the global configuration file could not be accessed or parsed.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `ProjectCreateError`
|
||||||
|
|
||||||
|
Signals that an error occurred when creating the project.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
"error" : {
|
||||||
|
"code" : 4012,
|
||||||
|
"message" : "Could not create the project."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `LoggingServiceUnavailable`
|
### `LoggingServiceUnavailable`
|
||||||
|
|
||||||
Signals that the logging service is not available.
|
Signals that the logging service is not available.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
"error" : {
|
"error" : {
|
||||||
"code" : 4012,
|
"code" : 4013,
|
||||||
"message" : "The logging service has failed to boot."
|
"message" : "The logging service has failed to boot."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<!-- encoders are assigned the type
|
|
||||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} %-5level [%-15thread] %-36logger{36} %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="org.enso" level="TRACE"/>
|
|
||||||
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
@ -14,6 +14,7 @@ import org.enso.languageserver.runtime.RuntimeKiller.{
|
|||||||
RuntimeShutdownResult,
|
RuntimeShutdownResult,
|
||||||
ShutDownRuntime
|
ShutDownRuntime
|
||||||
}
|
}
|
||||||
|
import org.enso.loggingservice.LogLevel
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{Await, Future}
|
import scala.concurrent.{Await, Future}
|
||||||
@ -21,8 +22,9 @@ import scala.concurrent.{Await, Future}
|
|||||||
/** A lifecycle component used to start and stop a Language Server.
|
/** A lifecycle component used to start and stop a Language Server.
|
||||||
*
|
*
|
||||||
* @param config a LS config
|
* @param config a LS config
|
||||||
|
* @param logLevel log level for the Language Server
|
||||||
*/
|
*/
|
||||||
class LanguageServerComponent(config: LanguageServerConfig)
|
class LanguageServerComponent(config: LanguageServerConfig, logLevel: LogLevel)
|
||||||
extends LifecycleComponent
|
extends LifecycleComponent
|
||||||
with LazyLogging {
|
with LazyLogging {
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ class LanguageServerComponent(config: LanguageServerConfig)
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def start(): Future[ComponentStarted.type] = {
|
override def start(): Future[ComponentStarted.type] = {
|
||||||
logger.info("Starting Language Server...")
|
logger.info("Starting Language Server...")
|
||||||
val module = new MainModule(config)
|
val module = new MainModule(config, logLevel)
|
||||||
val initMainModule =
|
val initMainModule =
|
||||||
for {
|
for {
|
||||||
_ <- module.init
|
_ <- module.init
|
||||||
|
@ -2,7 +2,6 @@ package org.enso.languageserver.boot
|
|||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.logging.ConsoleHandler
|
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import org.enso.jsonrpc.JsonRpcServer
|
import org.enso.jsonrpc.JsonRpcServer
|
||||||
@ -29,8 +28,8 @@ import org.enso.languageserver.runtime._
|
|||||||
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.text.BufferRegistry
|
import org.enso.languageserver.text.BufferRegistry
|
||||||
import org.enso.languageserver.util.Logging
|
|
||||||
import org.enso.languageserver.util.binary.BinaryEncoder
|
import org.enso.languageserver.util.binary.BinaryEncoder
|
||||||
|
import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel}
|
||||||
import org.enso.polyglot.{LanguageInfo, RuntimeOptions, RuntimeServerInfo}
|
import org.enso.polyglot.{LanguageInfo, RuntimeOptions, RuntimeServerInfo}
|
||||||
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
|
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
|
||||||
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
|
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
|
||||||
@ -45,8 +44,9 @@ import scala.util.{Failure, Success}
|
|||||||
/** A main module containing all components of the server.
|
/** A main module containing all components of the server.
|
||||||
*
|
*
|
||||||
* @param serverConfig configuration for the language server
|
* @param serverConfig configuration for the language server
|
||||||
|
* @param logLevel log level for the Language Server
|
||||||
*/
|
*/
|
||||||
class MainModule(serverConfig: LanguageServerConfig) {
|
class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
|
||||||
|
|
||||||
val log = LoggerFactory.getLogger(this.getClass)
|
val log = LoggerFactory.getLogger(this.getClass)
|
||||||
log.trace("Initializing...")
|
log.trace("Initializing...")
|
||||||
@ -150,21 +150,16 @@ class MainModule(serverConfig: LanguageServerConfig) {
|
|||||||
val stdIn = new ObservablePipedInputStream(stdInSink)
|
val stdIn = new ObservablePipedInputStream(stdInSink)
|
||||||
|
|
||||||
log.trace("Initializing Runtime context...")
|
log.trace("Initializing Runtime context...")
|
||||||
val logHandler = Logging.getLogHandler(LanguageInfo.ID) match {
|
|
||||||
case Left(t) =>
|
|
||||||
log.warn("Failed to create the Runtime logger", t)
|
|
||||||
new ConsoleHandler()
|
|
||||||
case Right(handler) =>
|
|
||||||
log.trace(s"Setting Runtime logger")
|
|
||||||
handler
|
|
||||||
}
|
|
||||||
val context = Context
|
val context = Context
|
||||||
.newBuilder(LanguageInfo.ID)
|
.newBuilder(LanguageInfo.ID)
|
||||||
.allowAllAccess(true)
|
.allowAllAccess(true)
|
||||||
.allowExperimentalOptions(true)
|
.allowExperimentalOptions(true)
|
||||||
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
|
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
|
||||||
.option(RuntimeOptions.PACKAGES_PATH, serverConfig.contentRootPath)
|
.option(RuntimeOptions.PACKAGES_PATH, serverConfig.contentRootPath)
|
||||||
.option(RuntimeOptions.LOG_LEVEL, logHandler.getLevel.toString)
|
.option(
|
||||||
|
RuntimeOptions.LOG_LEVEL,
|
||||||
|
JavaLoggingLogHandler.getJavaLogLevelFor(logLevel).getName
|
||||||
|
)
|
||||||
.option(
|
.option(
|
||||||
RuntimeServerInfo.JOB_PARALLELISM_OPTION,
|
RuntimeServerInfo.JOB_PARALLELISM_OPTION,
|
||||||
Runtime.getRuntime.availableProcessors().toString
|
Runtime.getRuntime.availableProcessors().toString
|
||||||
@ -172,7 +167,9 @@ class MainModule(serverConfig: LanguageServerConfig) {
|
|||||||
.out(stdOut)
|
.out(stdOut)
|
||||||
.err(stdErr)
|
.err(stdErr)
|
||||||
.in(stdIn)
|
.in(stdIn)
|
||||||
.logHandler(logHandler)
|
.logHandler(
|
||||||
|
JavaLoggingLogHandler.create(JavaLoggingLogHandler.defaultLevelMapping)
|
||||||
|
)
|
||||||
.serverTransport((uri: URI, peerEndpoint: MessageEndpoint) => {
|
.serverTransport((uri: URI, peerEndpoint: MessageEndpoint) => {
|
||||||
if (uri.toString == RuntimeServerInfo.URI) {
|
if (uri.toString == RuntimeServerInfo.URI) {
|
||||||
val connection = new RuntimeConnector.Endpoint(
|
val connection = new RuntimeConnector.Endpoint(
|
||||||
@ -187,8 +184,7 @@ class MainModule(serverConfig: LanguageServerConfig) {
|
|||||||
context.initialize(LanguageInfo.ID)
|
context.initialize(LanguageInfo.ID)
|
||||||
log.trace("Runtime context initialized")
|
log.trace("Runtime context initialized")
|
||||||
|
|
||||||
val logLevel = Logging.LogLevel.fromJava(logHandler.getLevel)
|
system.eventStream.setLogLevel(LogLevel.toAkka(logLevel))
|
||||||
system.eventStream.setLogLevel(Logging.LogLevel.toAkka(logLevel))
|
|
||||||
log.trace(s"Set akka log level to $logLevel")
|
log.trace(s"Set akka log level to $logLevel")
|
||||||
|
|
||||||
val runtimeKiller =
|
val runtimeKiller =
|
||||||
@ -267,9 +263,18 @@ class MainModule(serverConfig: LanguageServerConfig) {
|
|||||||
log.error("Failed to initialize SQL versions repo", ex)
|
log.error("Failed to initialize SQL versions repo", ex)
|
||||||
}(system.dispatcher)
|
}(system.dispatcher)
|
||||||
|
|
||||||
Future
|
val initialization = Future
|
||||||
.sequence(Seq(suggestionsRepoInit, versionsRepoInit))
|
.sequence(Seq(suggestionsRepoInit, versionsRepoInit))
|
||||||
.map(_ => ())
|
.map(_ => ())
|
||||||
|
|
||||||
|
initialization.onComplete {
|
||||||
|
case Success(()) =>
|
||||||
|
system.eventStream.publish(InitializedEvent.InitializationFinished)
|
||||||
|
case _ =>
|
||||||
|
system.eventStream.publish(InitializedEvent.InitializationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialization
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Close the main module releasing all resources. */
|
/** Close the main module releasing all resources. */
|
||||||
|
@ -7,4 +7,6 @@ object InitializedEvent {
|
|||||||
|
|
||||||
case object SuggestionsRepoInitialized extends InitializedEvent
|
case object SuggestionsRepoInitialized extends InitializedEvent
|
||||||
case object FileVersionsRepoInitialized extends InitializedEvent
|
case object FileVersionsRepoInitialized extends InitializedEvent
|
||||||
|
case object InitializationFinished extends InitializedEvent
|
||||||
|
case object InitializationFailed extends InitializedEvent
|
||||||
}
|
}
|
||||||
|
@ -17,4 +17,13 @@ object MonitoringApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case object InitialPing extends Method("heartbeat/init") {
|
||||||
|
implicit val hasParams = new HasParams[this.type] {
|
||||||
|
type Params = Unused.type
|
||||||
|
}
|
||||||
|
implicit val hasResult = new HasResult[this.type] {
|
||||||
|
type Result = Unused.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,15 @@ import org.enso.languageserver.filemanager.PathWatcherProtocol
|
|||||||
import org.enso.languageserver.io.InputOutputApi._
|
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.Ping
|
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
|
||||||
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._
|
||||||
import org.enso.languageserver.requesthandler.io._
|
import org.enso.languageserver.requesthandler.io._
|
||||||
import org.enso.languageserver.requesthandler.monitoring.PingHandler
|
import org.enso.languageserver.requesthandler.monitoring.{
|
||||||
|
InitialPingHandler,
|
||||||
|
PingHandler
|
||||||
|
}
|
||||||
import org.enso.languageserver.requesthandler.refactoring.RenameProjectHandler
|
import org.enso.languageserver.requesthandler.refactoring.RenameProjectHandler
|
||||||
import org.enso.languageserver.requesthandler.session.InitProtocolConnectionHandler
|
import org.enso.languageserver.requesthandler.session.InitProtocolConnectionHandler
|
||||||
import org.enso.languageserver.requesthandler.text._
|
import org.enso.languageserver.requesthandler.text._
|
||||||
@ -37,18 +40,12 @@ import org.enso.languageserver.requesthandler.visualisation.{
|
|||||||
}
|
}
|
||||||
import org.enso.languageserver.runtime.ContextRegistryProtocol
|
import org.enso.languageserver.runtime.ContextRegistryProtocol
|
||||||
import org.enso.languageserver.runtime.ExecutionApi._
|
import org.enso.languageserver.runtime.ExecutionApi._
|
||||||
import org.enso.languageserver.search.SearchApi.{
|
|
||||||
Completion,
|
|
||||||
GetSuggestionsDatabase,
|
|
||||||
GetSuggestionsDatabaseVersion,
|
|
||||||
Import,
|
|
||||||
InvalidateSuggestionsDatabase
|
|
||||||
}
|
|
||||||
import org.enso.languageserver.runtime.VisualisationApi.{
|
import org.enso.languageserver.runtime.VisualisationApi.{
|
||||||
AttachVisualisation,
|
AttachVisualisation,
|
||||||
DetachVisualisation,
|
DetachVisualisation,
|
||||||
ModifyVisualisation
|
ModifyVisualisation
|
||||||
}
|
}
|
||||||
|
import org.enso.languageserver.search.SearchApi._
|
||||||
import org.enso.languageserver.search.{SearchApi, SearchProtocol}
|
import org.enso.languageserver.search.{SearchApi, SearchProtocol}
|
||||||
import org.enso.languageserver.session.JsonSession
|
import org.enso.languageserver.session.JsonSession
|
||||||
import org.enso.languageserver.session.SessionApi.{
|
import org.enso.languageserver.session.SessionApi.{
|
||||||
@ -246,6 +243,7 @@ class JsonConnectionController(
|
|||||||
),
|
),
|
||||||
requestTimeout
|
requestTimeout
|
||||||
),
|
),
|
||||||
|
InitialPing -> InitialPingHandler.props,
|
||||||
AcquireCapability -> AcquireCapabilityHandler
|
AcquireCapability -> AcquireCapabilityHandler
|
||||||
.props(capabilityRouter, requestTimeout, rpcSession),
|
.props(capabilityRouter, requestTimeout, rpcSession),
|
||||||
ReleaseCapability -> ReleaseCapabilityHandler
|
ReleaseCapability -> ReleaseCapabilityHandler
|
||||||
|
@ -10,7 +10,7 @@ import org.enso.languageserver.capability.CapabilityApi.{
|
|||||||
}
|
}
|
||||||
import org.enso.languageserver.filemanager.FileManagerApi._
|
import org.enso.languageserver.filemanager.FileManagerApi._
|
||||||
import org.enso.languageserver.io.InputOutputApi._
|
import org.enso.languageserver.io.InputOutputApi._
|
||||||
import org.enso.languageserver.monitoring.MonitoringApi.Ping
|
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
|
||||||
import org.enso.languageserver.refactoring.RefactoringApi.RenameProject
|
import org.enso.languageserver.refactoring.RefactoringApi.RenameProject
|
||||||
import org.enso.languageserver.runtime.ExecutionApi._
|
import org.enso.languageserver.runtime.ExecutionApi._
|
||||||
import org.enso.languageserver.search.SearchApi._
|
import org.enso.languageserver.search.SearchApi._
|
||||||
@ -24,6 +24,7 @@ object JsonRpc {
|
|||||||
*/
|
*/
|
||||||
val protocol: Protocol = Protocol.empty
|
val protocol: Protocol = Protocol.empty
|
||||||
.registerRequest(Ping)
|
.registerRequest(Ping)
|
||||||
|
.registerRequest(InitialPing)
|
||||||
.registerRequest(InitProtocolConnection)
|
.registerRequest(InitProtocolConnection)
|
||||||
.registerRequest(AcquireCapability)
|
.registerRequest(AcquireCapability)
|
||||||
.registerRequest(ReleaseCapability)
|
.registerRequest(ReleaseCapability)
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package org.enso.languageserver.requesthandler.monitoring
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||||
|
import org.enso.jsonrpc.Errors.ServiceError
|
||||||
|
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult, Unused}
|
||||||
|
import org.enso.languageserver.event.InitializedEvent
|
||||||
|
import org.enso.languageserver.monitoring.MonitoringApi
|
||||||
|
|
||||||
|
/** A request handler for `heartbeat/init` commands. */
|
||||||
|
class InitialPingHandler extends Actor with ActorLogging {
|
||||||
|
|
||||||
|
override def preStart(): Unit = {
|
||||||
|
context.system.eventStream.subscribe(self, classOf[InitializedEvent])
|
||||||
|
}
|
||||||
|
|
||||||
|
override def receive: Receive = waitingForInitialization(Nil)
|
||||||
|
|
||||||
|
private def waitingForInitialization(
|
||||||
|
pendingRequests: List[(ActorRef, Id)]
|
||||||
|
): Receive = {
|
||||||
|
case Request(MonitoringApi.InitialPing, id, Unused) =>
|
||||||
|
context.become(
|
||||||
|
waitingForInitialization((sender() -> id) :: pendingRequests)
|
||||||
|
)
|
||||||
|
case InitializedEvent.InitializationFinished =>
|
||||||
|
for ((ref, id) <- pendingRequests) {
|
||||||
|
ref ! ResponseResult(MonitoringApi.InitialPing, id, Unused)
|
||||||
|
}
|
||||||
|
context.become(initialized)
|
||||||
|
case InitializedEvent.InitializationFailed =>
|
||||||
|
for ((ref, id) <- pendingRequests) {
|
||||||
|
ref ! ResponseError(Some(id), ServiceError)
|
||||||
|
}
|
||||||
|
context.become(failed)
|
||||||
|
case _: InitializedEvent =>
|
||||||
|
}
|
||||||
|
|
||||||
|
private def initialized: Receive = {
|
||||||
|
case Request(MonitoringApi.InitialPing, id, Unused) =>
|
||||||
|
sender() ! ResponseResult(MonitoringApi.InitialPing, id, Unused)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def failed: Receive = {
|
||||||
|
case Request(MonitoringApi.InitialPing, id, Unused) =>
|
||||||
|
ResponseError(Some(id), ServiceError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object InitialPingHandler {
|
||||||
|
|
||||||
|
/** Creates a configuration object used to create a
|
||||||
|
* [[InitialPingHandler]]
|
||||||
|
*
|
||||||
|
* @return a configuration object
|
||||||
|
*/
|
||||||
|
def props: Props = Props(new InitialPingHandler)
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package org.enso.languageserver.util
|
|
||||||
|
|
||||||
import java.util.logging.{Handler, Level, LogRecord}
|
|
||||||
|
|
||||||
import ch.qos.logback.classic
|
|
||||||
import org.slf4j.event
|
|
||||||
|
|
||||||
class LogHandler(logger: classic.Logger) extends Handler {
|
|
||||||
|
|
||||||
private val level = logger.getLevel
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def publish(record: LogRecord): Unit = {
|
|
||||||
logger.log(
|
|
||||||
null,
|
|
||||||
record.getSourceClassName,
|
|
||||||
LogHandler.toSlf4j(record.getLevel).toInt,
|
|
||||||
record.getMessage,
|
|
||||||
record.getParameters,
|
|
||||||
record.getThrown
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def flush(): Unit = ()
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def close(): Unit = ()
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getLevel: Level =
|
|
||||||
Logging.LogLevel.toJava(Logging.LogLevel.fromLogback(level))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object LogHandler {
|
|
||||||
|
|
||||||
/** Convert java utils log level to slf4j. */
|
|
||||||
private def toSlf4j(level: Level): event.Level =
|
|
||||||
Logging.LogLevel.toSlf4j(Logging.LogLevel.fromJava(level))
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
package org.enso.languageserver.util
|
|
||||||
|
|
||||||
import java.util
|
|
||||||
|
|
||||||
import cats.syntax.either._
|
|
||||||
import ch.qos.logback.classic.{Level, Logger, LoggerContext}
|
|
||||||
import org.slf4j.{event, LoggerFactory}
|
|
||||||
|
|
||||||
object Logging {
|
|
||||||
|
|
||||||
/** Application log level. */
|
|
||||||
sealed trait LogLevel
|
|
||||||
object LogLevel {
|
|
||||||
|
|
||||||
case object Error extends LogLevel
|
|
||||||
case object Warning extends LogLevel
|
|
||||||
case object Info extends LogLevel
|
|
||||||
case object Debug extends LogLevel
|
|
||||||
case object Trace extends LogLevel
|
|
||||||
|
|
||||||
/** Convert to logback log level. */
|
|
||||||
def toLogback(level: LogLevel): Level =
|
|
||||||
level match {
|
|
||||||
case Error => Level.ERROR
|
|
||||||
case Warning => Level.WARN
|
|
||||||
case Info => Level.INFO
|
|
||||||
case Debug => Level.DEBUG
|
|
||||||
case Trace => Level.TRACE
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert from logback log level. */
|
|
||||||
def fromLogback(level: Level): LogLevel = {
|
|
||||||
level match {
|
|
||||||
case Level.`ERROR` => Error
|
|
||||||
case Level.`WARN` => Warning
|
|
||||||
case Level.`INFO` => Info
|
|
||||||
case Level.`DEBUG` => Debug
|
|
||||||
case Level.`TRACE` => Trace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert to java util logging level. */
|
|
||||||
def toJava(level: LogLevel): util.logging.Level =
|
|
||||||
level match {
|
|
||||||
case Error => util.logging.Level.SEVERE
|
|
||||||
case Warning => util.logging.Level.WARNING
|
|
||||||
case Info => util.logging.Level.INFO
|
|
||||||
case Debug => util.logging.Level.FINE
|
|
||||||
case Trace => util.logging.Level.FINEST
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert from java util logging level. */
|
|
||||||
def fromJava(level: util.logging.Level): LogLevel =
|
|
||||||
level match {
|
|
||||||
case util.logging.Level.`SEVERE` => LogLevel.Error
|
|
||||||
case util.logging.Level.`WARNING` => LogLevel.Warning
|
|
||||||
case util.logging.Level.`INFO` => LogLevel.Info
|
|
||||||
case util.logging.Level.`CONFIG` => LogLevel.Debug
|
|
||||||
case util.logging.Level.`FINE` => LogLevel.Debug
|
|
||||||
case util.logging.Level.`FINER` => LogLevel.Debug
|
|
||||||
case util.logging.Level.`FINEST` => LogLevel.Trace
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert to slf4j logging level. */
|
|
||||||
def toSlf4j(level: LogLevel): event.Level =
|
|
||||||
level match {
|
|
||||||
case Error => event.Level.ERROR
|
|
||||||
case Warning => event.Level.WARN
|
|
||||||
case Info => event.Level.INFO
|
|
||||||
case Debug => event.Level.DEBUG
|
|
||||||
case Trace => event.Level.TRACE
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert to akka logging level. */
|
|
||||||
def toAkka(level: LogLevel): akka.event.Logging.LogLevel =
|
|
||||||
level match {
|
|
||||||
case Error => akka.event.Logging.ErrorLevel
|
|
||||||
case Warning => akka.event.Logging.WarningLevel
|
|
||||||
case Info => akka.event.Logging.InfoLevel
|
|
||||||
case Debug => akka.event.Logging.DebugLevel
|
|
||||||
case Trace => akka.event.Logging.DebugLevel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val ROOT_LOGGER = "org.enso"
|
|
||||||
|
|
||||||
/** Set log level for the application root logger.
|
|
||||||
*
|
|
||||||
* @param level the log level
|
|
||||||
* @return the new log level
|
|
||||||
*/
|
|
||||||
def setLogLevel(level: LogLevel): Either[Throwable, LogLevel] = {
|
|
||||||
Either.catchNonFatal {
|
|
||||||
val ctx = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
|
|
||||||
ctx.getLogger(ROOT_LOGGER).setLevel(LogLevel.toLogback(level))
|
|
||||||
level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get log level of the application root logger. */
|
|
||||||
def getLogLevel: Either[Throwable, LogLevel] = {
|
|
||||||
Either.catchNonFatal {
|
|
||||||
val ctx = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
|
|
||||||
val level = ctx.getLogger(ROOT_LOGGER).getLevel
|
|
||||||
LogLevel.fromLogback(level)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the application logger.
|
|
||||||
*
|
|
||||||
* @param name the logger name
|
|
||||||
* @return the application logger
|
|
||||||
*/
|
|
||||||
def getLogger(name: String): Either[Throwable, Logger] = {
|
|
||||||
Either.catchNonFatal {
|
|
||||||
val ctx = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
|
|
||||||
ctx.getLogger(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the java log handler instance backed by the application logger.
|
|
||||||
*
|
|
||||||
* @param name the logger name
|
|
||||||
* @return the application log handler
|
|
||||||
*/
|
|
||||||
def getLogHandler(name: String): Either[Throwable, LogHandler] =
|
|
||||||
for {
|
|
||||||
level <- Logging.getLogLevel
|
|
||||||
logger <- Logging.getLogger(name)
|
|
||||||
} yield {
|
|
||||||
logger.setLevel(Logging.LogLevel.toLogback(level))
|
|
||||||
new LogHandler(logger)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<!-- encoders are assigned the type
|
|
||||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%-15thread] %-5level %logger{36} %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="ERROR"/>
|
|
||||||
<logger name="slick" level="INFO"/>
|
|
||||||
<logger name="slick.compiler" level="INFO"/>
|
|
||||||
|
|
||||||
<root level="ERROR">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
@ -29,7 +29,11 @@ class PingHandlerSpec
|
|||||||
.props(List(subsystem1.ref, subsystem2.ref, subsystem3.ref), 10.seconds)
|
.props(List(subsystem1.ref, subsystem2.ref, subsystem3.ref), 10.seconds)
|
||||||
)
|
)
|
||||||
//when
|
//when
|
||||||
actorUnderTest ! Request(MonitoringApi.Ping, Number(1), Unused)
|
actorUnderTest ! Request(
|
||||||
|
MonitoringApi.Ping,
|
||||||
|
Number(1),
|
||||||
|
Unused
|
||||||
|
)
|
||||||
//then
|
//then
|
||||||
subsystem1.expectMsg(Ping)
|
subsystem1.expectMsg(Ping)
|
||||||
subsystem2.expectMsg(Ping)
|
subsystem2.expectMsg(Ping)
|
||||||
@ -48,7 +52,11 @@ class PingHandlerSpec
|
|||||||
.props(List(subsystem1.ref, subsystem2.ref, subsystem3.ref), 10.seconds)
|
.props(List(subsystem1.ref, subsystem2.ref, subsystem3.ref), 10.seconds)
|
||||||
)
|
)
|
||||||
//when
|
//when
|
||||||
actorUnderTest ! Request(MonitoringApi.Ping, Number(1), Unused)
|
actorUnderTest ! Request(
|
||||||
|
MonitoringApi.Ping,
|
||||||
|
Number(1),
|
||||||
|
Unused
|
||||||
|
)
|
||||||
subsystem1.expectMsg(Ping)
|
subsystem1.expectMsg(Ping)
|
||||||
subsystem1.lastSender ! Pong
|
subsystem1.lastSender ! Pong
|
||||||
subsystem2.expectMsg(Ping)
|
subsystem2.expectMsg(Ping)
|
||||||
@ -56,7 +64,9 @@ class PingHandlerSpec
|
|||||||
subsystem3.expectMsg(Ping)
|
subsystem3.expectMsg(Ping)
|
||||||
subsystem3.lastSender ! Pong
|
subsystem3.lastSender ! Pong
|
||||||
//then
|
//then
|
||||||
expectMsg(ResponseResult(MonitoringApi.Ping, Number(1), Unused))
|
expectMsg(
|
||||||
|
ResponseResult(MonitoringApi.Ping, Number(1), Unused)
|
||||||
|
)
|
||||||
//teardown
|
//teardown
|
||||||
system.stop(actorUnderTest)
|
system.stop(actorUnderTest)
|
||||||
}
|
}
|
||||||
@ -72,7 +82,11 @@ class PingHandlerSpec
|
|||||||
)
|
)
|
||||||
watch(actorUnderTest)
|
watch(actorUnderTest)
|
||||||
//when
|
//when
|
||||||
actorUnderTest ! Request(MonitoringApi.Ping, Number(1), Unused)
|
actorUnderTest ! Request(
|
||||||
|
MonitoringApi.Ping,
|
||||||
|
Number(1),
|
||||||
|
Unused
|
||||||
|
)
|
||||||
subsystem2.expectMsg(Ping)
|
subsystem2.expectMsg(Ping)
|
||||||
subsystem2.lastSender ! Pong
|
subsystem2.lastSender ! Pong
|
||||||
subsystem3.expectMsg(Ping)
|
subsystem3.expectMsg(Ping)
|
||||||
|
@ -154,6 +154,7 @@ class BaseServerTest extends JsonRpcServerTestKit {
|
|||||||
|
|
||||||
Await.ready(suggestionsRepoInit, timeout)
|
Await.ready(suggestionsRepoInit, timeout)
|
||||||
Await.ready(versionsRepoInit, timeout)
|
Await.ready(versionsRepoInit, timeout)
|
||||||
|
system.eventStream.publish(InitializedEvent.InitializationFinished)
|
||||||
|
|
||||||
new JsonConnectionControllerFactory(
|
new JsonConnectionControllerFactory(
|
||||||
bufferRegistry,
|
bufferRegistry,
|
||||||
|
@ -8,13 +8,13 @@ import org.enso.languageserver.search.Suggestions
|
|||||||
import org.enso.languageserver.websocket.json.{SearchJsonMessages => json}
|
import org.enso.languageserver.websocket.json.{SearchJsonMessages => json}
|
||||||
import org.enso.polyglot.data.Tree
|
import org.enso.polyglot.data.Tree
|
||||||
import org.enso.polyglot.runtime.Runtime.Api
|
import org.enso.polyglot.runtime.Runtime.Api
|
||||||
import org.enso.testkit.FlakySpec
|
import org.enso.testkit.RetrySpec
|
||||||
|
|
||||||
class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
|
class SuggestionsHandlerEventsTest extends BaseServerTest with RetrySpec {
|
||||||
|
|
||||||
"SuggestionsHandlerEvents" must {
|
"SuggestionsHandlerEvents" must {
|
||||||
|
|
||||||
"send suggestions database notifications" taggedAs Flaky in {
|
"send suggestions database notifications" taggedAs Retry in {
|
||||||
val client = getInitialisedWsClient()
|
val client = getInitialisedWsClient()
|
||||||
system.eventStream.publish(ProjectNameChangedEvent("Test", "Test"))
|
system.eventStream.publish(ProjectNameChangedEvent("Test", "Test"))
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
|||||||
.newProject(
|
.newProject(
|
||||||
path = actualPath,
|
path = actualPath,
|
||||||
name = name,
|
name = name,
|
||||||
version = version,
|
engineVersion = version,
|
||||||
authorName = globalConfig.authorName,
|
authorName = globalConfig.authorName,
|
||||||
authorEmail = globalConfig.authorEmail,
|
authorEmail = globalConfig.authorEmail,
|
||||||
additionalArguments = additionalArguments
|
additionalArguments = additionalArguments
|
||||||
@ -414,7 +414,9 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
|||||||
val (runtimeVersionRunSettings, whichEngine) = runner.version(useJSON).get
|
val (runtimeVersionRunSettings, whichEngine) = runner.version(useJSON).get
|
||||||
|
|
||||||
val isEngineInstalled =
|
val isEngineInstalled =
|
||||||
componentsManager.findEngine(runtimeVersionRunSettings.version).isDefined
|
componentsManager
|
||||||
|
.findEngine(runtimeVersionRunSettings.engineVersion)
|
||||||
|
.isDefined
|
||||||
val runtimeVersionString = if (isEngineInstalled) {
|
val runtimeVersionString = if (isEngineInstalled) {
|
||||||
val output = runner.withCommand(
|
val output = runner.withCommand(
|
||||||
runtimeVersionRunSettings,
|
runtimeVersionRunSettings,
|
||||||
|
@ -120,14 +120,14 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
.newProject(
|
.newProject(
|
||||||
path = projectPath,
|
path = projectPath,
|
||||||
name = "ProjectName",
|
name = "ProjectName",
|
||||||
version = defaultEngineVersion,
|
engineVersion = defaultEngineVersion,
|
||||||
authorName = Some(authorName),
|
authorName = Some(authorName),
|
||||||
authorEmail = Some(authorEmail),
|
authorEmail = Some(authorEmail),
|
||||||
additionalArguments = Seq(additionalArgument)
|
additionalArguments = Seq(additionalArgument)
|
||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual defaultEngineVersion
|
runSettings.engineVersion shouldEqual defaultEngineVersion
|
||||||
runSettings.runnerArguments should contain(additionalArgument)
|
runSettings.runnerArguments should contain(additionalArgument)
|
||||||
val commandLine = runSettings.runnerArguments.mkString(" ")
|
val commandLine = runSettings.runnerArguments.mkString(" ")
|
||||||
commandLine should include(
|
commandLine should include(
|
||||||
@ -149,7 +149,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual defaultEngineVersion
|
runSettings.engineVersion shouldEqual defaultEngineVersion
|
||||||
runSettings.runnerArguments should (contain("arg") and contain("--flag"))
|
runSettings.runnerArguments should (contain("arg") and contain("--flag"))
|
||||||
runSettings.runnerArguments.mkString(" ") should
|
runSettings.runnerArguments.mkString(" ") should
|
||||||
(include("--repl") and not include s"--in-project")
|
(include("--repl") and not include s"--in-project")
|
||||||
@ -174,7 +174,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
outsideProject.version shouldEqual version
|
outsideProject.engineVersion shouldEqual version
|
||||||
outsideProject.runnerArguments.mkString(" ") should
|
outsideProject.runnerArguments.mkString(" ") should
|
||||||
(include(s"--in-project $normalizedPath") and include("--repl"))
|
(include(s"--in-project $normalizedPath") and include("--repl"))
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
insideProject.version shouldEqual version
|
insideProject.engineVersion shouldEqual version
|
||||||
insideProject.runnerArguments.mkString(" ") should
|
insideProject.runnerArguments.mkString(" ") should
|
||||||
(include(s"--in-project $normalizedPath") and include("--repl"))
|
(include(s"--in-project $normalizedPath") and include("--repl"))
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
overriddenRun.version shouldEqual overridden
|
overriddenRun.engineVersion shouldEqual overridden
|
||||||
overriddenRun.runnerArguments.mkString(" ") should
|
overriddenRun.runnerArguments.mkString(" ") should
|
||||||
(include(s"--in-project $normalizedPath") and include("--repl"))
|
(include(s"--in-project $normalizedPath") and include("--repl"))
|
||||||
}
|
}
|
||||||
@ -230,7 +230,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual version
|
runSettings.engineVersion shouldEqual version
|
||||||
val commandLine = runSettings.runnerArguments.mkString(" ")
|
val commandLine = runSettings.runnerArguments.mkString(" ")
|
||||||
commandLine should include(s"--interface ${options.interface}")
|
commandLine should include(s"--interface ${options.interface}")
|
||||||
commandLine should include(s"--rpc-port ${options.rpcPort}")
|
commandLine should include(s"--rpc-port ${options.rpcPort}")
|
||||||
@ -250,7 +250,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
logLevel = LogLevel.Info
|
logLevel = LogLevel.Info
|
||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
.version shouldEqual overridden
|
.engineVersion shouldEqual overridden
|
||||||
}
|
}
|
||||||
|
|
||||||
"run a project" in {
|
"run a project" in {
|
||||||
@ -270,7 +270,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
outsideProject.version shouldEqual version
|
outsideProject.engineVersion shouldEqual version
|
||||||
outsideProject.runnerArguments.mkString(" ") should
|
outsideProject.runnerArguments.mkString(" ") should
|
||||||
include(s"--run $normalizedPath")
|
include(s"--run $normalizedPath")
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
insideProject.version shouldEqual version
|
insideProject.engineVersion shouldEqual version
|
||||||
insideProject.runnerArguments.mkString(" ") should
|
insideProject.runnerArguments.mkString(" ") should
|
||||||
include(s"--run $normalizedPath")
|
include(s"--run $normalizedPath")
|
||||||
|
|
||||||
@ -298,7 +298,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
overriddenRun.version shouldEqual overridden
|
overriddenRun.engineVersion shouldEqual overridden
|
||||||
overriddenRun.runnerArguments.mkString(" ") should
|
overriddenRun.runnerArguments.mkString(" ") should
|
||||||
include(s"--run $normalizedPath")
|
include(s"--run $normalizedPath")
|
||||||
|
|
||||||
@ -338,7 +338,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual defaultEngineVersion
|
runSettings.engineVersion shouldEqual defaultEngineVersion
|
||||||
runSettings.runnerArguments.mkString(" ") should
|
runSettings.runnerArguments.mkString(" ") should
|
||||||
(include(s"--run $normalizedPath") and (not(include("--in-project"))))
|
(include(s"--run $normalizedPath") and (not(include("--in-project"))))
|
||||||
}
|
}
|
||||||
@ -362,7 +362,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual version
|
runSettings.engineVersion shouldEqual version
|
||||||
runSettings.runnerArguments.mkString(" ") should
|
runSettings.runnerArguments.mkString(" ") should
|
||||||
(include(s"--run $normalizedFilePath") and
|
(include(s"--run $normalizedFilePath") and
|
||||||
include(s"--in-project $normalizedProjectPath"))
|
include(s"--in-project $normalizedProjectPath"))
|
||||||
@ -374,7 +374,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
.version(useJSON = true)
|
.version(useJSON = true)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual defaultEngineVersion
|
runSettings.engineVersion shouldEqual defaultEngineVersion
|
||||||
runSettings.runnerArguments should
|
runSettings.runnerArguments should
|
||||||
(contain("--version") and contain("--json"))
|
(contain("--version") and contain("--json"))
|
||||||
|
|
||||||
@ -391,7 +391,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
|||||||
.version(useJSON = false)
|
.version(useJSON = false)
|
||||||
.get
|
.get
|
||||||
|
|
||||||
runSettings.version shouldEqual version
|
runSettings.engineVersion shouldEqual version
|
||||||
runSettings.runnerArguments should
|
runSettings.runnerArguments should
|
||||||
(contain("--version") and not(contain("--json")))
|
(contain("--version") and not(contain("--json")))
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class UpgradeSpec
|
|||||||
*
|
*
|
||||||
* If `launcherVersion` is not provided, the default one is used.
|
* If `launcherVersion` is not provided, the default one is used.
|
||||||
*
|
*
|
||||||
* It waits a 100ms delay after creating the launcher copy to ensure that the
|
* It waits a 250ms delay after creating the launcher copy to ensure that the
|
||||||
* copy can be called right away after calling this function. It is not
|
* copy can be called right away after calling this function. It is not
|
||||||
* absolutely certain that this is helpful, but from time to time, the tests
|
* absolutely certain that this is helpful, but from time to time, the tests
|
||||||
* fail because the filesystem does not allow to access the executable as
|
* fail because the filesystem does not allow to access the executable as
|
||||||
@ -94,7 +94,7 @@ class UpgradeSpec
|
|||||||
val root = launcherPath.getParent.getParent
|
val root = launcherPath.getParent.getParent
|
||||||
FileSystem.writeTextFile(root / ".enso.portable", "mark")
|
FileSystem.writeTextFile(root / ".enso.portable", "mark")
|
||||||
}
|
}
|
||||||
Thread.sleep(100)
|
Thread.sleep(250)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Path to the launcher executable in the temporary distribution.
|
/** Path to the launcher executable in the temporary distribution.
|
||||||
@ -156,7 +156,7 @@ class UpgradeSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
"upgrade" should {
|
"upgrade" should {
|
||||||
"upgrade to latest version (excluding broken)" in {
|
"upgrade to latest version (excluding broken)" taggedAs Retry in {
|
||||||
prepareDistribution(
|
prepareDistribution(
|
||||||
portable = true,
|
portable = true,
|
||||||
launcherVersion = Some(SemVer(0, 0, 2))
|
launcherVersion = Some(SemVer(0, 0, 2))
|
||||||
@ -166,7 +166,7 @@ class UpgradeSpec
|
|||||||
checkVersion() shouldEqual SemVer(0, 0, 4)
|
checkVersion() shouldEqual SemVer(0, 0, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
"not downgrade without being explicitly asked to do so" in {
|
"not downgrade without being explicitly asked to do so" taggedAs Retry in {
|
||||||
// precondition for the test to make sense
|
// precondition for the test to make sense
|
||||||
SemVer(buildinfo.Info.ensoVersion).value should be > SemVer(0, 0, 4)
|
SemVer(buildinfo.Info.ensoVersion).value should be > SemVer(0, 0, 4)
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ class UpgradeSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
"upgrade/downgrade to a specific version " +
|
"upgrade/downgrade to a specific version " +
|
||||||
"(and update necessary files)" in {
|
"(and update necessary files)" taggedAs Retry in {
|
||||||
// precondition for the test to make sense
|
// precondition for the test to make sense
|
||||||
SemVer(buildinfo.Info.ensoVersion).value should be > SemVer(0, 0, 4)
|
SemVer(buildinfo.Info.ensoVersion).value should be > SemVer(0, 0, 4)
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ class UpgradeSpec
|
|||||||
.trim shouldEqual "Test license"
|
.trim shouldEqual "Test license"
|
||||||
}
|
}
|
||||||
|
|
||||||
"upgrade also in installed mode" in {
|
"upgrade also in installed mode" taggedAs Retry in {
|
||||||
prepareDistribution(
|
prepareDistribution(
|
||||||
portable = false,
|
portable = false,
|
||||||
launcherVersion = Some(SemVer(0, 0, 0))
|
launcherVersion = Some(SemVer(0, 0, 0))
|
||||||
@ -228,37 +228,46 @@ class UpgradeSpec
|
|||||||
)
|
)
|
||||||
|
|
||||||
checkVersion() shouldEqual SemVer(0, 0, 0)
|
checkVersion() shouldEqual SemVer(0, 0, 0)
|
||||||
run(Seq("upgrade", "0.0.3")) should returnSuccess
|
val process = startLauncher(Seq("upgrade", "0.0.3"))
|
||||||
|
try {
|
||||||
|
process.join(timeoutSeconds = 30) should returnSuccess
|
||||||
|
|
||||||
checkVersion() shouldEqual SemVer(0, 0, 3)
|
checkVersion() shouldEqual SemVer(0, 0, 3)
|
||||||
|
|
||||||
val launchedVersions = Seq(
|
val launchedVersions = Seq(
|
||||||
"0.0.0",
|
"0.0.0",
|
||||||
"0.0.0",
|
"0.0.0",
|
||||||
"0.0.1",
|
"0.0.1",
|
||||||
"0.0.2",
|
"0.0.2",
|
||||||
"0.0.3"
|
"0.0.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
val reportedLaunchLog = TestHelpers
|
val reportedLaunchLog = TestHelpers
|
||||||
.readFileContent(launcherPath.getParent / ".launcher_version_log")
|
.readFileContent(launcherPath.getParent / ".launcher_version_log")
|
||||||
.trim
|
.trim
|
||||||
.linesIterator
|
.linesIterator
|
||||||
.toSeq
|
.toSeq
|
||||||
|
|
||||||
reportedLaunchLog shouldEqual launchedVersions
|
reportedLaunchLog shouldEqual launchedVersions
|
||||||
|
|
||||||
withClue(
|
withClue(
|
||||||
"After the update we run the version check, running the launcher " +
|
"After the update we run the version check, running the launcher " +
|
||||||
"after the update should ensure no leftover temporary executables " +
|
"after the update should ensure no leftover temporary executables " +
|
||||||
"are left in the bin directory."
|
"are left in the bin directory."
|
||||||
) {
|
) {
|
||||||
val binDirectory = launcherPath.getParent
|
val binDirectory = launcherPath.getParent
|
||||||
val leftOverExecutables = FileSystem
|
val leftOverExecutables = FileSystem
|
||||||
.listDirectory(binDirectory)
|
.listDirectory(binDirectory)
|
||||||
.map(_.getFileName.toString)
|
.map(_.getFileName.toString)
|
||||||
.filter(_.startsWith("enso"))
|
.filter(_.startsWith("enso"))
|
||||||
leftOverExecutables shouldEqual Seq(OS.executableName("enso"))
|
leftOverExecutables shouldEqual Seq(OS.executableName("enso"))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (process.isAlive) {
|
||||||
|
// ensure that the child process frees resources if retrying the test
|
||||||
|
process.kill()
|
||||||
|
Thread.sleep(500)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,8 +280,8 @@ class UpgradeSpec
|
|||||||
val enginesPath = getTestDirectory / "enso" / "dist"
|
val enginesPath = getTestDirectory / "enso" / "dist"
|
||||||
Files.createDirectories(enginesPath)
|
Files.createDirectories(enginesPath)
|
||||||
|
|
||||||
// TODO [RW] re-enable this test when #1046 is done and the engine
|
// TODO [RW] re-enable this test when #1046 or #1273 is done and the
|
||||||
// distribution can be used in the test
|
// engine distribution can be used in the test
|
||||||
// FileSystem.copyDirectory(
|
// FileSystem.copyDirectory(
|
||||||
// Path.of("target/distribution/"),
|
// Path.of("target/distribution/"),
|
||||||
// enginesPath / "0.1.0"
|
// enginesPath / "0.1.0"
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.enso.runner
|
||||||
|
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
|
||||||
|
/** A helper object that allows to access current version of the runner.
|
||||||
|
*
|
||||||
|
* The current version is parsed from [[buildinfo]], but in development mode it
|
||||||
|
* can be overridden by setting `enso.version.override` property. This is used
|
||||||
|
* in project-manager tests to override the version of projects created using
|
||||||
|
* the runner.
|
||||||
|
*/
|
||||||
|
object CurrentVersion {
|
||||||
|
|
||||||
|
/** The version that the application should report. */
|
||||||
|
lazy val version: SemVer = computeVersion()
|
||||||
|
|
||||||
|
private def computeVersion(): SemVer = {
|
||||||
|
val buildVersion = SemVer(buildinfo.Info.ensoVersion).getOrElse {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Fatal error: Enso version included in buildinfo is not a valid " +
|
||||||
|
"semver string, this should never happen."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (buildinfo.Info.isRelease) buildVersion
|
||||||
|
else
|
||||||
|
sys.props
|
||||||
|
.get("enso.version.override")
|
||||||
|
.flatMap(SemVer(_))
|
||||||
|
.getOrElse(buildVersion)
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import org.enso.languageserver.boot.{
|
|||||||
LanguageServerComponent,
|
LanguageServerComponent,
|
||||||
LanguageServerConfig
|
LanguageServerConfig
|
||||||
}
|
}
|
||||||
|
import org.enso.loggingservice.LogLevel
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
@ -16,10 +17,11 @@ object LanguageServerApp {
|
|||||||
/** Runs a Language Server
|
/** Runs a Language Server
|
||||||
*
|
*
|
||||||
* @param config a config
|
* @param config a config
|
||||||
|
* @param logLevel log level
|
||||||
*/
|
*/
|
||||||
def run(config: LanguageServerConfig): Unit = {
|
def run(config: LanguageServerConfig, logLevel: LogLevel): Unit = {
|
||||||
println("Starting Language Server...")
|
println("Starting Language Server...")
|
||||||
val server = new LanguageServerComponent(config)
|
val server = new LanguageServerComponent(config, logLevel)
|
||||||
Await.result(server.start(), 10.seconds)
|
Await.result(server.start(), 10.seconds)
|
||||||
StdIn.readLine()
|
StdIn.readLine()
|
||||||
Await.result(server.stop(), 10.seconds)
|
Await.result(server.stop(), 10.seconds)
|
||||||
|
@ -5,7 +5,6 @@ import java.util.UUID
|
|||||||
|
|
||||||
import akka.http.scaladsl.model.{IllegalUriException, Uri}
|
import akka.http.scaladsl.model.{IllegalUriException, Uri}
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import nl.gn0s1s.bump.SemVer
|
|
||||||
import org.apache.commons.cli.{Option => CliOption, _}
|
import org.apache.commons.cli.{Option => CliOption, _}
|
||||||
import org.enso.languageserver.boot
|
import org.enso.languageserver.boot
|
||||||
import org.enso.languageserver.boot.LanguageServerConfig
|
import org.enso.languageserver.boot.LanguageServerConfig
|
||||||
@ -234,19 +233,13 @@ object Main {
|
|||||||
): Unit = {
|
): Unit = {
|
||||||
val root = new File(path)
|
val root = new File(path)
|
||||||
val name = nameOption.getOrElse(PackageManager.Default.generateName(root))
|
val name = nameOption.getOrElse(PackageManager.Default.generateName(root))
|
||||||
val currentVersion = SemVer(buildinfo.Info.ensoVersion).getOrElse {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Fatal error: Enso version included in buildinfo is not a valid " +
|
|
||||||
"semver string, this should never happen."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val authors =
|
val authors =
|
||||||
if (authorName.isEmpty && authorEmail.isEmpty) List()
|
if (authorName.isEmpty && authorEmail.isEmpty) List()
|
||||||
else List(Contact(name = authorName, email = authorEmail))
|
else List(Contact(name = authorName, email = authorEmail))
|
||||||
PackageManager.Default.create(
|
PackageManager.Default.create(
|
||||||
root = root,
|
root = root,
|
||||||
name = name,
|
name = name,
|
||||||
ensoVersion = SemVerEnsoVersion(currentVersion),
|
ensoVersion = SemVerEnsoVersion(CurrentVersion.version),
|
||||||
authors = authors,
|
authors = authors,
|
||||||
maintainers = authors
|
maintainers = authors
|
||||||
)
|
)
|
||||||
@ -432,8 +425,6 @@ object Main {
|
|||||||
* @param logLevel log level to set for the engine runtime
|
* @param logLevel log level to set for the engine runtime
|
||||||
*/
|
*/
|
||||||
private def runLanguageServer(line: CommandLine, logLevel: LogLevel): Unit = {
|
private def runLanguageServer(line: CommandLine, logLevel: LogLevel): Unit = {
|
||||||
val _ = logLevel // TODO [RW] handle logging in the Language Server (#1144)
|
|
||||||
|
|
||||||
val maybeConfig = parseSeverOptions(line)
|
val maybeConfig = parseSeverOptions(line)
|
||||||
|
|
||||||
maybeConfig match {
|
maybeConfig match {
|
||||||
@ -442,7 +433,7 @@ object Main {
|
|||||||
exitFail()
|
exitFail()
|
||||||
|
|
||||||
case Right(config) =>
|
case Right(config) =>
|
||||||
LanguageServerApp.run(config)
|
LanguageServerApp.run(config, logLevel)
|
||||||
exitSuccess()
|
exitSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,7 +472,8 @@ object Main {
|
|||||||
def displayVersion(useJson: Boolean): Unit = {
|
def displayVersion(useJson: Boolean): Unit = {
|
||||||
val versionDescription = VersionDescription.make(
|
val versionDescription = VersionDescription.make(
|
||||||
"Enso Compiler and Runtime",
|
"Enso Compiler and Runtime",
|
||||||
includeRuntimeJVMInfo = true
|
includeRuntimeJVMInfo = true,
|
||||||
|
customVersion = Some(CurrentVersion.version.toString)
|
||||||
)
|
)
|
||||||
println(versionDescription.asString(useJson))
|
println(versionDescription.asString(useJson))
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ abstract class JsonRpcServerTestKit
|
|||||||
val _ = binding.unbind()
|
val _ = binding.unbind()
|
||||||
}
|
}
|
||||||
|
|
||||||
class WsTestClient(address: String) {
|
class WsTestClient(address: String, debugMessages: Boolean = false) {
|
||||||
private var inActor: ActorRef = _
|
private var inActor: ActorRef = _
|
||||||
private val outActor: TestProbe = TestProbe()
|
private val outActor: TestProbe = TestProbe()
|
||||||
private val source: Source[Message, NotUsed] = Source
|
private val source: Source[Message, NotUsed] = Source
|
||||||
@ -104,8 +104,11 @@ abstract class JsonRpcServerTestKit
|
|||||||
|
|
||||||
def send(json: Json): Unit = send(json.noSpaces)
|
def send(json: Json): Unit = send(json.noSpaces)
|
||||||
|
|
||||||
def expectMessage(timeout: FiniteDuration = 3.seconds.dilated): String =
|
def expectMessage(timeout: FiniteDuration = 3.seconds.dilated): String = {
|
||||||
outActor.expectMsgClass[String](timeout, classOf[String])
|
val message = outActor.expectMsgClass[String](timeout, classOf[String])
|
||||||
|
if (debugMessages) println(message)
|
||||||
|
message
|
||||||
|
}
|
||||||
|
|
||||||
def expectJson(
|
def expectJson(
|
||||||
json: Json,
|
json: Json,
|
||||||
|
@ -15,6 +15,7 @@ sealed abstract class LogLevel(final val level: Int) {
|
|||||||
def shouldLog(other: LogLevel): Boolean =
|
def shouldLog(other: LogLevel): Boolean =
|
||||||
other.level <= level
|
other.level <= level
|
||||||
}
|
}
|
||||||
|
|
||||||
object LogLevel {
|
object LogLevel {
|
||||||
|
|
||||||
/** This log level should not be used by messages, instead it can be set as
|
/** This log level should not be used by messages, instead it can be set as
|
||||||
@ -93,19 +94,38 @@ object LogLevel {
|
|||||||
level.level.asJson
|
level.level.asJson
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates a [[LogLevel]] from its integer representation.
|
||||||
|
*
|
||||||
|
* Returns None if the number does not represent a valid log level.
|
||||||
|
*/
|
||||||
|
def fromInteger(level: Int): Option[LogLevel] = level match {
|
||||||
|
case Error.level => Some(Error)
|
||||||
|
case Warning.level => Some(Warning)
|
||||||
|
case Info.level => Some(Info)
|
||||||
|
case Debug.level => Some(Debug)
|
||||||
|
case Trace.level => Some(Trace)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
|
||||||
/** [[Decoder]] instance for [[LogLevel]].
|
/** [[Decoder]] instance for [[LogLevel]].
|
||||||
*/
|
*/
|
||||||
implicit val decoder: Decoder[LogLevel] = { json =>
|
implicit val decoder: Decoder[LogLevel] = { json =>
|
||||||
json.as[Int].flatMap {
|
json.as[Int].flatMap { level =>
|
||||||
case Error.level => Right(Error)
|
fromInteger(level).toRight(
|
||||||
case Warning.level => Right(Warning)
|
DecodingFailure(s"`$level` is not a valid log level.", json.history)
|
||||||
case Info.level => Right(Info)
|
)
|
||||||
case Debug.level => Right(Debug)
|
|
||||||
case Trace.level => Right(Trace)
|
|
||||||
case other =>
|
|
||||||
Left(
|
|
||||||
DecodingFailure(s"`$other` is not a valid log level.", json.history)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Converts our internal [[LogLevel]] to the corresponding instance of
|
||||||
|
* Akka-specific log level.
|
||||||
|
*/
|
||||||
|
def toAkka(logLevel: LogLevel): akka.event.Logging.LogLevel = logLevel match {
|
||||||
|
case Off => akka.event.Logging.LogLevel(Int.MinValue)
|
||||||
|
case Error => akka.event.Logging.ErrorLevel
|
||||||
|
case Warning => akka.event.Logging.WarningLevel
|
||||||
|
case Info => akka.event.Logging.InfoLevel
|
||||||
|
case Debug => akka.event.Logging.DebugLevel
|
||||||
|
case Trace => akka.event.Logging.DebugLevel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
package org.enso.loggingservice
|
package org.enso.loggingservice
|
||||||
|
|
||||||
import org.enso.loggingservice.internal.service.{Client, Local, Server, Service}
|
import org.enso.loggingservice.internal.service.{Client, Local, Server, Service}
|
||||||
import org.enso.loggingservice.internal.{
|
import org.enso.loggingservice.internal._
|
||||||
BlockingConsumerMessageQueue,
|
|
||||||
InternalLogMessage,
|
|
||||||
InternalLogger,
|
|
||||||
LoggerConnection
|
|
||||||
}
|
|
||||||
import org.enso.loggingservice.printers.{Printer, StderrPrinter}
|
import org.enso.loggingservice.printers.{Printer, StderrPrinter}
|
||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
@ -14,8 +9,37 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||||||
/** Manages the logging service.
|
/** Manages the logging service.
|
||||||
*/
|
*/
|
||||||
object LoggingServiceManager {
|
object LoggingServiceManager {
|
||||||
private val messageQueue = new BlockingConsumerMessageQueue()
|
private val testLoggingPropertyKey = "org.enso.loggingservice.test-log-level"
|
||||||
private var currentLevel: LogLevel = LogLevel.Trace
|
|
||||||
|
private var currentService: Option[Service] = None
|
||||||
|
private var currentLevel: LogLevel = LogLevel.Trace
|
||||||
|
|
||||||
|
/** Creates an instance for the [[messageQueue]].
|
||||||
|
*
|
||||||
|
* Runs special workaround logic if test mode is detected.
|
||||||
|
*/
|
||||||
|
private def initializeMessageQueue(): BlockingConsumerMessageQueue = {
|
||||||
|
sys.props.get(testLoggingPropertyKey) match {
|
||||||
|
case Some(value) =>
|
||||||
|
val logLevel =
|
||||||
|
value.toIntOption.flatMap(LogLevel.fromInteger).getOrElse {
|
||||||
|
System.err.println(
|
||||||
|
s"Invalid log level for $testLoggingPropertyKey, " +
|
||||||
|
s"falling back to info."
|
||||||
|
)
|
||||||
|
LogLevel.Info
|
||||||
|
}
|
||||||
|
|
||||||
|
val shouldOverride = () => currentService.isEmpty
|
||||||
|
|
||||||
|
new TestMessageQueue(logLevel, shouldOverride)
|
||||||
|
case None => productionMessageQueue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def productionMessageQueue() = new BlockingConsumerMessageQueue()
|
||||||
|
|
||||||
|
private val messageQueue = initializeMessageQueue()
|
||||||
|
|
||||||
/** The default [[LoggerConnection]] that should be used by all backends which
|
/** The default [[LoggerConnection]] that should be used by all backends which
|
||||||
* want to use the logging service.
|
* want to use the logging service.
|
||||||
@ -32,8 +56,6 @@ object LoggingServiceManager {
|
|||||||
override def logLevel: LogLevel = currentLevel
|
override def logLevel: LogLevel = currentLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentService: Option[Service] = None
|
|
||||||
|
|
||||||
/** Sets up the logging service, but in a separate thread to avoid stalling
|
/** Sets up the logging service, but in a separate thread to avoid stalling
|
||||||
* the application.
|
* the application.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.enso.loggingservice.internal
|
||||||
|
import org.enso.loggingservice.LogLevel
|
||||||
|
import org.enso.loggingservice.internal.protocol.WSLogMessage
|
||||||
|
import org.enso.loggingservice.printers.StderrPrinter
|
||||||
|
|
||||||
|
/** A message queue for use in testing.
|
||||||
|
*
|
||||||
|
* It has a smaller buffer and ignores messages from a certain log level.
|
||||||
|
*
|
||||||
|
* @param logLevel
|
||||||
|
* @param shouldOverride
|
||||||
|
*/
|
||||||
|
class TestMessageQueue(logLevel: LogLevel, shouldOverride: () => Boolean)
|
||||||
|
extends BlockingConsumerMessageQueue(bufferSize = 100) {
|
||||||
|
|
||||||
|
private def shouldKeepMessage(
|
||||||
|
message: Either[InternalLogMessage, WSLogMessage]
|
||||||
|
): Boolean = message match {
|
||||||
|
case Left(value) => logLevel.shouldLog(value.level)
|
||||||
|
case Right(value) => logLevel.shouldLog(value.level)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val overridePrinter = StderrPrinter.create()
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def send(message: Either[InternalLogMessage, WSLogMessage]): Unit =
|
||||||
|
if (shouldKeepMessage(message)) {
|
||||||
|
if (shouldOverride())
|
||||||
|
overridePrinter.print(message.fold(_.toLogMessage, identity))
|
||||||
|
else
|
||||||
|
super.send(message)
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import org.enso.projectmanager.control.core.{Applicative, CovariantFlatMap}
|
|||||||
import org.enso.projectmanager.control.effect.{Async, ErrorChannel, Exec, Sync}
|
import org.enso.projectmanager.control.effect.{Async, ErrorChannel, Exec, Sync}
|
||||||
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem
|
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.{
|
import org.enso.projectmanager.infrastructure.languageserver.{
|
||||||
|
ExecutorWithUnlimitedPool,
|
||||||
LanguageServerGatewayImpl,
|
LanguageServerGatewayImpl,
|
||||||
LanguageServerRegistry,
|
LanguageServerRegistry,
|
||||||
ShutdownHookActivator
|
ShutdownHookActivator
|
||||||
@ -25,6 +26,7 @@ import org.enso.projectmanager.service.config.GlobalConfigService
|
|||||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementService
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementService
|
||||||
import org.enso.projectmanager.service.{
|
import org.enso.projectmanager.service.{
|
||||||
MonadicProjectValidator,
|
MonadicProjectValidator,
|
||||||
|
ProjectCreationService,
|
||||||
ProjectService,
|
ProjectService,
|
||||||
ProjectServiceFailure,
|
ProjectServiceFailure,
|
||||||
ValidationFailure
|
ValidationFailure
|
||||||
@ -76,7 +78,9 @@ class MainModule[
|
|||||||
config.network,
|
config.network,
|
||||||
config.bootloader,
|
config.bootloader,
|
||||||
config.supervision,
|
config.supervision,
|
||||||
config.timeout
|
config.timeout,
|
||||||
|
DefaultDistributionConfiguration,
|
||||||
|
ExecutorWithUnlimitedPool
|
||||||
),
|
),
|
||||||
"language-server-registry"
|
"language-server-registry"
|
||||||
)
|
)
|
||||||
@ -94,19 +98,25 @@ class MainModule[
|
|||||||
config.timeout
|
config.timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lazy val projectCreationService =
|
||||||
|
new ProjectCreationService[F](DefaultDistributionConfiguration)
|
||||||
|
|
||||||
|
lazy val globalConfigService =
|
||||||
|
new GlobalConfigService[F](DefaultDistributionConfiguration)
|
||||||
|
|
||||||
lazy val projectService =
|
lazy val projectService =
|
||||||
new ProjectService[F](
|
new ProjectService[F](
|
||||||
projectValidator,
|
projectValidator,
|
||||||
projectRepository,
|
projectRepository,
|
||||||
|
projectCreationService,
|
||||||
|
globalConfigService,
|
||||||
logging,
|
logging,
|
||||||
clock,
|
clock,
|
||||||
gen,
|
gen,
|
||||||
languageServerGateway
|
languageServerGateway,
|
||||||
|
DefaultDistributionConfiguration
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val globalConfigService =
|
|
||||||
new GlobalConfigService[F](DefaultDistributionConfiguration)
|
|
||||||
|
|
||||||
lazy val runtimeVersionManagementService =
|
lazy val runtimeVersionManagementService =
|
||||||
new RuntimeVersionManagementService[F](DefaultDistributionConfiguration)
|
new RuntimeVersionManagementService[F](DefaultDistributionConfiguration)
|
||||||
|
|
||||||
|
@ -8,7 +8,10 @@ import akka.pattern.pipe
|
|||||||
import akka.stream.scaladsl.{Flow, Sink, Source}
|
import akka.stream.scaladsl.{Flow, Sink, Source}
|
||||||
import akka.stream.{CompletionStrategy, OverflowStrategy}
|
import akka.stream.{CompletionStrategy, OverflowStrategy}
|
||||||
import org.enso.projectmanager.infrastructure.http.AkkaBasedWebSocketConnection._
|
import org.enso.projectmanager.infrastructure.http.AkkaBasedWebSocketConnection._
|
||||||
import org.enso.projectmanager.infrastructure.http.FanOutReceiver.Listen
|
import org.enso.projectmanager.infrastructure.http.FanOutReceiver.{
|
||||||
|
Attach,
|
||||||
|
Detach
|
||||||
|
}
|
||||||
import org.enso.projectmanager.infrastructure.http.WebSocketConnection.{
|
import org.enso.projectmanager.infrastructure.http.WebSocketConnection.{
|
||||||
WebSocketConnected,
|
WebSocketConnected,
|
||||||
WebSocketMessage,
|
WebSocketMessage,
|
||||||
@ -65,7 +68,11 @@ class AkkaBasedWebSocketConnection(address: String)(implicit
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def attachListener(listener: ActorRef): Unit =
|
override def attachListener(listener: ActorRef): Unit =
|
||||||
receiver ! Listen(listener)
|
receiver ! Attach(listener)
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def detachListener(listener: ActorRef): Unit =
|
||||||
|
receiver ! Detach(listener)
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
def connect(): Unit = {
|
def connect(): Unit = {
|
||||||
@ -83,6 +90,7 @@ class AkkaBasedWebSocketConnection(address: String)(implicit
|
|||||||
case InvalidUpgradeResponse(_, cause) =>
|
case InvalidUpgradeResponse(_, cause) =>
|
||||||
WebSocketStreamFailure(new Exception(s"Cannot connect $cause"))
|
WebSocketStreamFailure(new Exception(s"Cannot connect $cause"))
|
||||||
}
|
}
|
||||||
|
.recover(WebSocketStreamFailure(_))
|
||||||
.pipeTo(receiver)
|
.pipeTo(receiver)
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package org.enso.projectmanager.infrastructure.http
|
package org.enso.projectmanager.infrastructure.http
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef}
|
import akka.actor.{Actor, ActorRef}
|
||||||
import org.enso.projectmanager.infrastructure.http.FanOutReceiver.Listen
|
import org.enso.projectmanager.infrastructure.http.FanOutReceiver.{
|
||||||
|
Attach,
|
||||||
|
Detach
|
||||||
|
}
|
||||||
|
|
||||||
/** A fan-out receiver that delivers messages to multiple listeners.
|
/** A fan-out receiver that delivers messages to multiple listeners.
|
||||||
*/
|
*/
|
||||||
@ -10,7 +13,8 @@ class FanOutReceiver extends Actor {
|
|||||||
override def receive: Receive = running()
|
override def receive: Receive = running()
|
||||||
|
|
||||||
private def running(listeners: Set[ActorRef] = Set.empty): Receive = {
|
private def running(listeners: Set[ActorRef] = Set.empty): Receive = {
|
||||||
case Listen(listener) => context.become(running(listeners + listener))
|
case Attach(listener) => context.become(running(listeners + listener))
|
||||||
|
case Detach(listener) => context.become(running(listeners - listener))
|
||||||
case msg => listeners.foreach(_ ! msg)
|
case msg => listeners.foreach(_ ! msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +26,6 @@ object FanOutReceiver {
|
|||||||
*
|
*
|
||||||
* @param listener a listener to attach
|
* @param listener a listener to attach
|
||||||
*/
|
*/
|
||||||
case class Listen(listener: ActorRef)
|
case class Attach(listener: ActorRef)
|
||||||
|
case class Detach(listener: ActorRef)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,12 @@ trait WebSocketConnection {
|
|||||||
*/
|
*/
|
||||||
def attachListener(listener: ActorRef): Unit
|
def attachListener(listener: ActorRef): Unit
|
||||||
|
|
||||||
|
/** Removes the listener of incoming messages.
|
||||||
|
*
|
||||||
|
* Can be useful when disconnecting has timed out and we do not want to
|
||||||
|
* receive the disconnected message after the owning actor is long dead.
|
||||||
|
*/
|
||||||
|
def detachListener(listener: ActorRef): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
object WebSocketConnection {
|
object WebSocketConnection {
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.lang.ProcessBuilder.Redirect
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import org.apache.commons.lang3.concurrent.BasicThreadFactory
|
||||||
|
import org.enso.loggingservice.LogLevel
|
||||||
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
|
||||||
|
import org.enso.runtimeversionmanager.runner.{LanguageServerOptions, Runner}
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import scala.util.Using
|
||||||
|
|
||||||
|
object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
|
||||||
|
|
||||||
|
/** An executor that ensures each job runs in a separate thread.
|
||||||
|
*
|
||||||
|
* It is used to run the process in a background thread. It is blocking by
|
||||||
|
* design to ensure that the locking API is used correctly. This executor
|
||||||
|
* should start no more than one thread per Language Server instance.
|
||||||
|
*/
|
||||||
|
private val forkedProcessExecutor = {
|
||||||
|
val threadFactory =
|
||||||
|
new BasicThreadFactory.Builder()
|
||||||
|
.namingPattern("language-server-pool-%d")
|
||||||
|
.build()
|
||||||
|
Executors.newCachedThreadPool(threadFactory)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def spawn(
|
||||||
|
descriptor: LanguageServerDescriptor,
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
lifecycleListener: LanguageServerExecutor.LifecycleListener
|
||||||
|
): Unit = {
|
||||||
|
val runnable: Runnable = { () =>
|
||||||
|
try {
|
||||||
|
runServer(
|
||||||
|
descriptor,
|
||||||
|
progressTracker,
|
||||||
|
rpcPort,
|
||||||
|
dataPort,
|
||||||
|
lifecycleListener
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
case throwable: Throwable =>
|
||||||
|
lifecycleListener.onFailed(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forkedProcessExecutor.submit(runnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Runs the child process, ensuring that the proper locks are kept for the
|
||||||
|
* used engine for the whole lifetime of that process.
|
||||||
|
*
|
||||||
|
* Returns the exit code of the process. This function is blocking so it
|
||||||
|
* should be run in a backgroung thread.
|
||||||
|
*/
|
||||||
|
private def runServer(
|
||||||
|
descriptor: LanguageServerDescriptor,
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
lifecycleListener: LanguageServerExecutor.LifecycleListener
|
||||||
|
): Unit = {
|
||||||
|
val distributionConfiguration = descriptor.distributionConfiguration
|
||||||
|
val versionManager = RuntimeVersionManagerFactory(distributionConfiguration)
|
||||||
|
.makeRuntimeVersionManager(progressTracker)
|
||||||
|
|
||||||
|
// TODO [RW] logging #1151
|
||||||
|
val loggerConnection = Future.successful(None)
|
||||||
|
val logLevel = LogLevel.Info
|
||||||
|
val options = LanguageServerOptions(
|
||||||
|
rootId = descriptor.rootId,
|
||||||
|
interface = descriptor.networkConfig.interface,
|
||||||
|
rpcPort = rpcPort,
|
||||||
|
dataPort = dataPort
|
||||||
|
)
|
||||||
|
|
||||||
|
val runner = new Runner(
|
||||||
|
versionManager,
|
||||||
|
distributionConfiguration.environment,
|
||||||
|
loggerConnection
|
||||||
|
)
|
||||||
|
val runSettings = runner
|
||||||
|
.startLanguageServer(
|
||||||
|
options = options,
|
||||||
|
projectPath = descriptor.rootPath,
|
||||||
|
version = descriptor.engineVersion,
|
||||||
|
logLevel = logLevel,
|
||||||
|
additionalArguments = Seq()
|
||||||
|
)
|
||||||
|
.get
|
||||||
|
runner.withCommand(runSettings, descriptor.jvmSettings) { command =>
|
||||||
|
val process = {
|
||||||
|
val pb = command.builder()
|
||||||
|
pb.inheritIO()
|
||||||
|
|
||||||
|
pb.redirectInput(Redirect.PIPE)
|
||||||
|
if (descriptor.discardOutput) {
|
||||||
|
pb.redirectError(Redirect.DISCARD)
|
||||||
|
pb.redirectOutput(Redirect.DISCARD)
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleListener.onStarted(new LanguageServerProcessHandle(process))
|
||||||
|
val exitCode = process.waitFor()
|
||||||
|
lifecycleListener.onTerminated(exitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LanguageServerProcessHandle(private val process: Process)
|
||||||
|
extends LanguageServerExecutor.ProcessHandle {
|
||||||
|
|
||||||
|
/** Requests the child process to terminate gracefully by sending the
|
||||||
|
* termination request to its standard input stream.
|
||||||
|
*/
|
||||||
|
def requestGracefulTermination(): Unit =
|
||||||
|
Using(new PrintWriter(process.getOutputStream)) { writer =>
|
||||||
|
writer.println()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
def kill(): Unit = {
|
||||||
|
process.destroyForcibly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,10 @@ import org.enso.projectmanager.infrastructure.languageserver.HeartbeatSession.{
|
|||||||
HeartbeatTimeout,
|
HeartbeatTimeout,
|
||||||
SocketClosureTimeout
|
SocketClosureTimeout
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerSupervisor.ServerUnresponsive
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerSupervisor.{
|
||||||
|
HeartbeatReceived,
|
||||||
|
ServerUnresponsive
|
||||||
|
}
|
||||||
import org.enso.projectmanager.util.UnhandledLogging
|
import org.enso.projectmanager.util.UnhandledLogging
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
@ -28,12 +31,21 @@ import scala.concurrent.duration.FiniteDuration
|
|||||||
* @param timeout a session timeout
|
* @param timeout a session timeout
|
||||||
* @param connectionFactory a web socket connection factory
|
* @param connectionFactory a web socket connection factory
|
||||||
* @param scheduler a scheduler
|
* @param scheduler a scheduler
|
||||||
|
* @param method api method to use for the heartbeat message
|
||||||
|
* @param sendConfirmations whether to send [[HeartbeatReceived]] to confirm
|
||||||
|
* that a response has been received
|
||||||
|
* @param quietErrors if set, reports errors in debug level instead of error
|
||||||
|
* level, can be used when errors are expected (i.e. on
|
||||||
|
* startup)
|
||||||
*/
|
*/
|
||||||
class HeartbeatSession(
|
class HeartbeatSession(
|
||||||
socket: Socket,
|
socket: Socket,
|
||||||
timeout: FiniteDuration,
|
timeout: FiniteDuration,
|
||||||
connectionFactory: WebSocketConnectionFactory,
|
connectionFactory: WebSocketConnectionFactory,
|
||||||
scheduler: Scheduler
|
scheduler: Scheduler,
|
||||||
|
method: String,
|
||||||
|
sendConfirmations: Boolean,
|
||||||
|
quietErrors: Boolean
|
||||||
) extends Actor
|
) extends Actor
|
||||||
with ActorLogging
|
with ActorLogging
|
||||||
with UnhandledLogging {
|
with UnhandledLogging {
|
||||||
@ -57,7 +69,7 @@ class HeartbeatSession(
|
|||||||
connection.send(s"""
|
connection.send(s"""
|
||||||
|{
|
|{
|
||||||
| "jsonrpc": "2.0",
|
| "jsonrpc": "2.0",
|
||||||
| "method": "heartbeat/ping",
|
| "method": "$method",
|
||||||
| "id": "$requestId",
|
| "id": "$requestId",
|
||||||
| "params": null
|
| "params": null
|
||||||
|}
|
|}
|
||||||
@ -66,7 +78,7 @@ class HeartbeatSession(
|
|||||||
context.become(pongStage(cancellable))
|
context.become(pongStage(cancellable))
|
||||||
|
|
||||||
case WebSocketStreamFailure(th) =>
|
case WebSocketStreamFailure(th) =>
|
||||||
log.error(th, s"An error occurred during connecting to websocket $socket")
|
logError(th, s"An error occurred during connecting to websocket $socket")
|
||||||
context.parent ! ServerUnresponsive
|
context.parent ! ServerUnresponsive
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
@ -81,16 +93,18 @@ class HeartbeatSession(
|
|||||||
|
|
||||||
maybeJson match {
|
maybeJson match {
|
||||||
case Left(error) =>
|
case Left(error) =>
|
||||||
log.error(error, "An error occurred during parsing pong reply")
|
logError(error, "An error occurred during parsing pong reply")
|
||||||
|
|
||||||
case Right(id) =>
|
case Right(id) =>
|
||||||
if (id == requestId.toString) {
|
if (id == requestId.toString) {
|
||||||
log.debug(s"Received correct pong message from $socket")
|
log.debug(s"Received correct pong message from $socket")
|
||||||
|
|
||||||
|
if (sendConfirmations) {
|
||||||
|
context.parent ! HeartbeatReceived
|
||||||
|
}
|
||||||
|
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
connection.disconnect()
|
stop()
|
||||||
val closureTimeout =
|
|
||||||
scheduler.scheduleOnce(timeout, self, SocketClosureTimeout)
|
|
||||||
context.become(socketClosureStage(closureTimeout))
|
|
||||||
} else {
|
} else {
|
||||||
log.warning(s"Received unknown response $payload")
|
log.warning(s"Received unknown response $payload")
|
||||||
}
|
}
|
||||||
@ -99,21 +113,17 @@ class HeartbeatSession(
|
|||||||
case HeartbeatTimeout =>
|
case HeartbeatTimeout =>
|
||||||
log.debug(s"Heartbeat timeout detected for $requestId")
|
log.debug(s"Heartbeat timeout detected for $requestId")
|
||||||
context.parent ! ServerUnresponsive
|
context.parent ! ServerUnresponsive
|
||||||
connection.disconnect()
|
stop()
|
||||||
val closureTimeout =
|
|
||||||
scheduler.scheduleOnce(timeout, self, SocketClosureTimeout)
|
|
||||||
context.become(socketClosureStage(closureTimeout))
|
|
||||||
|
|
||||||
case WebSocketStreamClosed =>
|
case WebSocketStreamClosed =>
|
||||||
context.parent ! ServerUnresponsive
|
context.parent ! ServerUnresponsive
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
|
||||||
case WebSocketStreamFailure(th) =>
|
case WebSocketStreamFailure(th) =>
|
||||||
log.error(th, s"An error occurred during waiting for Pong message")
|
logError(th, s"An error occurred during waiting for Pong message")
|
||||||
context.parent ! ServerUnresponsive
|
context.parent ! ServerUnresponsive
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
connection.disconnect()
|
stop()
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case GracefulStop =>
|
case GracefulStop =>
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
@ -126,13 +136,14 @@ class HeartbeatSession(
|
|||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
|
|
||||||
case WebSocketStreamFailure(th) =>
|
case WebSocketStreamFailure(th) =>
|
||||||
log.error(th, s"An error occurred during closing web socket")
|
logError(th, s"An error occurred during closing web socket")
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
|
|
||||||
case SocketClosureTimeout =>
|
case SocketClosureTimeout =>
|
||||||
log.error(s"Socket closure timed out")
|
logError(s"Socket closure timed out")
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
connection.detachListener(self)
|
||||||
|
|
||||||
case GracefulStop => // ignoring it, because the actor is already closing
|
case GracefulStop => // ignoring it, because the actor is already closing
|
||||||
}
|
}
|
||||||
@ -144,6 +155,22 @@ class HeartbeatSession(
|
|||||||
context.become(socketClosureStage(closureTimeout))
|
context.become(socketClosureStage(closureTimeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def logError(throwable: Throwable, message: String): Unit = {
|
||||||
|
if (quietErrors) {
|
||||||
|
log.debug(s"$message ($throwable)")
|
||||||
|
} else {
|
||||||
|
log.error(throwable, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def logError(message: String): Unit = {
|
||||||
|
if (quietErrors) {
|
||||||
|
log.debug(message)
|
||||||
|
} else {
|
||||||
|
log.error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object HeartbeatSession {
|
object HeartbeatSession {
|
||||||
@ -156,7 +183,8 @@ object HeartbeatSession {
|
|||||||
*/
|
*/
|
||||||
case object SocketClosureTimeout
|
case object SocketClosureTimeout
|
||||||
|
|
||||||
/** Creates a configuration object used to create a [[LanguageServerSupervisor]].
|
/** Creates a configuration object used to create an ordinary
|
||||||
|
* [[HeartbeatSession]] for monitoring server's status.
|
||||||
*
|
*
|
||||||
* @param socket a server socket
|
* @param socket a server socket
|
||||||
* @param timeout a session timeout
|
* @param timeout a session timeout
|
||||||
@ -170,6 +198,43 @@ object HeartbeatSession {
|
|||||||
connectionFactory: WebSocketConnectionFactory,
|
connectionFactory: WebSocketConnectionFactory,
|
||||||
scheduler: Scheduler
|
scheduler: Scheduler
|
||||||
): Props =
|
): Props =
|
||||||
Props(new HeartbeatSession(socket, timeout, connectionFactory, scheduler))
|
Props(
|
||||||
|
new HeartbeatSession(
|
||||||
|
socket,
|
||||||
|
timeout,
|
||||||
|
connectionFactory,
|
||||||
|
scheduler,
|
||||||
|
"heartbeat/ping",
|
||||||
|
sendConfirmations = false,
|
||||||
|
quietErrors = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Creates a configuration object used to create an initial
|
||||||
|
* [[HeartbeatSession]] for checking if the server has finished booting.
|
||||||
|
*
|
||||||
|
* @param socket a server socket
|
||||||
|
* @param timeout a session timeout
|
||||||
|
* @param connectionFactory a web socket connection factory
|
||||||
|
* @param scheduler a scheduler
|
||||||
|
* @return a configuration object
|
||||||
|
*/
|
||||||
|
def initialProps(
|
||||||
|
socket: Socket,
|
||||||
|
timeout: FiniteDuration,
|
||||||
|
connectionFactory: WebSocketConnectionFactory,
|
||||||
|
scheduler: Scheduler
|
||||||
|
): Props =
|
||||||
|
Props(
|
||||||
|
new HeartbeatSession(
|
||||||
|
socket,
|
||||||
|
timeout,
|
||||||
|
connectionFactory,
|
||||||
|
scheduler,
|
||||||
|
"heartbeat/init",
|
||||||
|
sendConfirmations = true,
|
||||||
|
quietErrors = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,52 @@
|
|||||||
package org.enso.projectmanager.infrastructure.languageserver
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
|
||||||
import akka.actor.Status.Failure
|
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||||
import akka.actor.{Actor, ActorLogging, Props}
|
|
||||||
import akka.pattern.pipe
|
|
||||||
import org.enso.languageserver.boot.{
|
|
||||||
LanguageServerComponent,
|
|
||||||
LanguageServerConfig
|
|
||||||
}
|
|
||||||
import org.enso.projectmanager.boot.configuration.BootloaderConfig
|
import org.enso.projectmanager.boot.configuration.BootloaderConfig
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerBootLoader.{
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerBootLoader.{
|
||||||
Boot,
|
|
||||||
FindFreeSocket,
|
|
||||||
ServerBootFailed,
|
ServerBootFailed,
|
||||||
ServerBooted
|
ServerBooted
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.infrastructure.net.Tcp
|
import org.enso.projectmanager.infrastructure.net.Tcp
|
||||||
import org.enso.projectmanager.util.UnhandledLogging
|
import org.enso.projectmanager.util.UnhandledLogging
|
||||||
|
|
||||||
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
/** It boots a Language Sever described by the `descriptor`. Upon boot failure
|
/** It boots a Language Sever described by the `descriptor`. Upon boot failure
|
||||||
* looks up new available port and retries to boot the server.
|
* looks up new available port and retries to boot the server.
|
||||||
*
|
*
|
||||||
|
* Once the server is booted it can restart it on request.
|
||||||
|
*
|
||||||
|
* The `bootProgressTracker` has to be provided because, while the process
|
||||||
|
* assumes that the required engine version has been pre-installed, it still
|
||||||
|
* may sometimes need to wait on a lock (for example if an engine is being
|
||||||
|
* uninstalled), so these events need to be reported to the progress tracker.
|
||||||
|
*
|
||||||
|
* @param bootProgressTracker an [[ActorRef]] that will get progress updates
|
||||||
|
* related to initializing the engine
|
||||||
* @param descriptor a LS descriptor
|
* @param descriptor a LS descriptor
|
||||||
* @param config a bootloader config
|
* @param config a bootloader config
|
||||||
|
* @param bootTimeout a finite duration that determines the deadline for
|
||||||
|
* initialization of the server process, if the process
|
||||||
|
* starts but fails to initialize after this timeout, it is
|
||||||
|
* treated as a boot failure and the process is gracefully
|
||||||
|
* stopped
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
*/
|
*/
|
||||||
class LanguageServerBootLoader(
|
class LanguageServerBootLoader(
|
||||||
|
bootProgressTracker: ActorRef,
|
||||||
descriptor: LanguageServerDescriptor,
|
descriptor: LanguageServerDescriptor,
|
||||||
config: BootloaderConfig
|
config: BootloaderConfig,
|
||||||
|
bootTimeout: FiniteDuration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
) extends Actor
|
) extends Actor
|
||||||
with ActorLogging
|
with ActorLogging
|
||||||
with UnhandledLogging {
|
with UnhandledLogging {
|
||||||
|
|
||||||
|
// TODO [RW] consider adding a stop timeout so that if the graceful stop on
|
||||||
|
// timed-out boot also does not work, the process can be killed forcibly
|
||||||
|
// (#1315)
|
||||||
|
|
||||||
import context.dispatcher
|
import context.dispatcher
|
||||||
|
|
||||||
override def preStart(): Unit = {
|
override def preStart(): Unit = {
|
||||||
@ -39,6 +56,10 @@ class LanguageServerBootLoader(
|
|||||||
|
|
||||||
override def receive: Receive = findingSocket()
|
override def receive: Receive = findingSocket()
|
||||||
|
|
||||||
|
/** First bootloader phase - looking for a free set of ports for the server.
|
||||||
|
*
|
||||||
|
* Once the ports are found, the process starts booting.
|
||||||
|
*/
|
||||||
private def findingSocket(retry: Int = 0): Receive = {
|
private def findingSocket(retry: Int = 0): Receive = {
|
||||||
case FindFreeSocket =>
|
case FindFreeSocket =>
|
||||||
log.debug("Looking for available socket to bind the language server")
|
log.debug("Looking for available socket to bind the language server")
|
||||||
@ -53,53 +74,190 @@ class LanguageServerBootLoader(
|
|||||||
s"binary:${descriptor.networkConfig.interface}:$binaryPort]"
|
s"binary:${descriptor.networkConfig.interface}:$binaryPort]"
|
||||||
)
|
)
|
||||||
self ! Boot
|
self ! Boot
|
||||||
context.become(booting(jsonRpcPort, binaryPort, retry))
|
context.become(
|
||||||
|
bootingFirstTime(
|
||||||
|
rpcPort = jsonRpcPort,
|
||||||
|
dataPort = binaryPort,
|
||||||
|
retryCount = retry
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
case GracefulStop =>
|
case GracefulStop =>
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def booting(rpcPort: Int, dataPort: Int, retryCount: Int): Receive = {
|
/** This phase is triggered when the ports are found.
|
||||||
|
*
|
||||||
|
* When booting for the first time, the actor that will be notified is the
|
||||||
|
* parent and retries are allowed (upon retry new set of ports will be tried).
|
||||||
|
*/
|
||||||
|
private def bootingFirstTime(
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
retryCount: Int
|
||||||
|
): Receive = booting(
|
||||||
|
rpcPort = rpcPort,
|
||||||
|
dataPort = dataPort,
|
||||||
|
shouldRetry = true,
|
||||||
|
retryCount = retryCount,
|
||||||
|
bootRequester = context.parent
|
||||||
|
)
|
||||||
|
|
||||||
|
/** A general booting phase.
|
||||||
|
*
|
||||||
|
* It spawns the [[LanguageServerProcess]] child actor and waits for it to
|
||||||
|
* confirm that the server has been successfully initialized (or failed to
|
||||||
|
* boot). Once booted it enters the running phase.
|
||||||
|
*/
|
||||||
|
private def booting(
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
shouldRetry: Boolean,
|
||||||
|
retryCount: Int,
|
||||||
|
bootRequester: ActorRef
|
||||||
|
): Receive = {
|
||||||
case Boot =>
|
case Boot =>
|
||||||
log.debug("Booting a language server")
|
log.debug("Booting a language server")
|
||||||
val config = LanguageServerConfig(
|
context.actorOf(
|
||||||
descriptor.networkConfig.interface,
|
LanguageServerProcess.props(
|
||||||
rpcPort,
|
progressTracker = bootProgressTracker,
|
||||||
dataPort,
|
descriptor = descriptor,
|
||||||
descriptor.rootId,
|
bootTimeout = bootTimeout,
|
||||||
descriptor.root,
|
rpcPort = rpcPort,
|
||||||
descriptor.name,
|
dataPort = dataPort,
|
||||||
context.dispatcher
|
executor = executor
|
||||||
|
),
|
||||||
|
s"process-wrapper-${descriptor.name}"
|
||||||
)
|
)
|
||||||
val server = new LanguageServerComponent(config)
|
|
||||||
server.start().map(_ => config -> server) pipeTo self
|
|
||||||
|
|
||||||
case Failure(th) =>
|
case LanguageServerProcess.ServerTerminated(exitCode) =>
|
||||||
log.error(
|
handleBootFailure(
|
||||||
th,
|
shouldRetry,
|
||||||
s"An error occurred during boot of Language Server [${descriptor.name}]"
|
retryCount,
|
||||||
|
bootRequester,
|
||||||
|
s"Language server terminated with exit code $exitCode before " +
|
||||||
|
s"finishing booting.",
|
||||||
|
None
|
||||||
)
|
)
|
||||||
if (retryCount < config.numberOfRetries) {
|
|
||||||
context.system.scheduler
|
case LanguageServerProcess.ServerThreadFailed(throwable) =>
|
||||||
.scheduleOnce(config.delayBetweenRetry, self, FindFreeSocket)
|
handleBootFailure(
|
||||||
context.become(findingSocket(retryCount + 1))
|
shouldRetry,
|
||||||
} else {
|
retryCount,
|
||||||
|
bootRequester,
|
||||||
|
s"Language server thread failed with $throwable.",
|
||||||
|
Some(throwable)
|
||||||
|
)
|
||||||
|
|
||||||
|
case LanguageServerProcess.ServerConfirmedFinishedBooting =>
|
||||||
|
val connectionInfo = LanguageServerConnectionInfo(
|
||||||
|
descriptor.networkConfig.interface,
|
||||||
|
rpcPort = rpcPort,
|
||||||
|
dataPort = dataPort
|
||||||
|
)
|
||||||
|
log.info(s"Language server booted [$connectionInfo].")
|
||||||
|
|
||||||
|
bootRequester ! ServerBooted(connectionInfo, self)
|
||||||
|
context.become(running(connectionInfo))
|
||||||
|
|
||||||
|
case GracefulStop =>
|
||||||
|
context.children.foreach(_ ! LanguageServerProcess.Stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handles a boot failure by logging it and depending on configuration,
|
||||||
|
* retrying or notifying the proper actor about the failure.
|
||||||
|
*/
|
||||||
|
private def handleBootFailure(
|
||||||
|
shouldRetry: Boolean,
|
||||||
|
retryCount: Int,
|
||||||
|
bootRequester: ActorRef,
|
||||||
|
message: String,
|
||||||
|
throwable: Option[Throwable]
|
||||||
|
): Unit = {
|
||||||
|
log.warning(message)
|
||||||
|
|
||||||
|
if (shouldRetry && retryCount < config.numberOfRetries) {
|
||||||
|
context.system.scheduler
|
||||||
|
.scheduleOnce(config.delayBetweenRetry, self, FindFreeSocket)
|
||||||
|
context.become(findingSocket(retryCount + 1))
|
||||||
|
} else {
|
||||||
|
if (shouldRetry) {
|
||||||
log.error(
|
log.error(
|
||||||
s"Tried $retryCount times to boot Language Server. Giving up."
|
s"Tried $retryCount times to boot Language Server. Giving up."
|
||||||
)
|
)
|
||||||
context.parent ! ServerBootFailed(th)
|
} else {
|
||||||
context.stop(self)
|
log.error("Failed to restart the server. Giving up.")
|
||||||
}
|
}
|
||||||
|
bootRequester ! ServerBootFailed(
|
||||||
case (config: LanguageServerConfig, server: LanguageServerComponent) =>
|
throwable.getOrElse(new RuntimeException(message))
|
||||||
log.info(s"Language server booted [$config].")
|
)
|
||||||
context.parent ! ServerBooted(config, server)
|
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** After successful boot, we cannot stop as it would stop our child process,
|
||||||
|
* so we just wait for it to terminate.
|
||||||
|
*
|
||||||
|
* The restart command can trigger the restarting phase which consists of two
|
||||||
|
* parts: waiting for the old process to shutdown and rebooting a new one.
|
||||||
|
*/
|
||||||
|
private def running(connectionInfo: LanguageServerConnectionInfo): Receive = {
|
||||||
|
case msg @ LanguageServerProcess.ServerTerminated(exitCode) =>
|
||||||
|
log.debug(
|
||||||
|
s"Language Server process has terminated with exit code $exitCode"
|
||||||
|
)
|
||||||
|
context.parent ! msg
|
||||||
|
context.stop(self)
|
||||||
|
|
||||||
|
case Restart =>
|
||||||
|
context.children.foreach(_ ! LanguageServerProcess.Stop)
|
||||||
|
context.become(
|
||||||
|
restartingWaitingForShutdown(connectionInfo, rebootRequester = sender())
|
||||||
|
)
|
||||||
|
|
||||||
case GracefulStop =>
|
case GracefulStop =>
|
||||||
context.stop(self)
|
context.children.foreach(_ ! LanguageServerProcess.Stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO [RW] handling stop timeout (#1315)
|
||||||
|
// may also consider a stop timeout for GracefulStop and killing the process?
|
||||||
|
/** First phase of restart waits fot the old process to shutdown and boots the
|
||||||
|
* new process.
|
||||||
|
*/
|
||||||
|
def restartingWaitingForShutdown(
|
||||||
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
|
rebootRequester: ActorRef
|
||||||
|
): Receive = {
|
||||||
|
case LanguageServerProcess.ServerTerminated(exitCode) =>
|
||||||
|
log.debug(
|
||||||
|
s"Language Server process has terminated (as requested to reboot) " +
|
||||||
|
s"with exit code $exitCode"
|
||||||
|
)
|
||||||
|
|
||||||
|
context.become(rebooting(connectionInfo, rebootRequester))
|
||||||
|
self ! Boot
|
||||||
|
|
||||||
|
case GracefulStop =>
|
||||||
|
context.children.foreach(_ ! LanguageServerProcess.Stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Currently rebooting does not retry.
|
||||||
|
*
|
||||||
|
* We cannot directly re-use the retry logic from the initial boot, because
|
||||||
|
* we need to keep the ports unchanged, since they are already passed to
|
||||||
|
* other components that will try to connect there.
|
||||||
|
*/
|
||||||
|
def rebooting(
|
||||||
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
|
rebootRequester: ActorRef
|
||||||
|
): Receive = booting(
|
||||||
|
rpcPort = connectionInfo.rpcPort,
|
||||||
|
dataPort = connectionInfo.dataPort,
|
||||||
|
shouldRetry = false,
|
||||||
|
retryCount = config.numberOfRetries,
|
||||||
|
bootRequester = rebootRequester
|
||||||
|
)
|
||||||
|
|
||||||
private def findPort(): Int =
|
private def findPort(): Int =
|
||||||
Tcp.findAvailablePort(
|
Tcp.findAvailablePort(
|
||||||
descriptor.networkConfig.interface,
|
descriptor.networkConfig.interface,
|
||||||
@ -107,29 +265,41 @@ class LanguageServerBootLoader(
|
|||||||
descriptor.networkConfig.maxPort
|
descriptor.networkConfig.maxPort
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private case object FindFreeSocket
|
||||||
|
private case object Boot
|
||||||
}
|
}
|
||||||
|
|
||||||
object LanguageServerBootLoader {
|
object LanguageServerBootLoader {
|
||||||
|
|
||||||
/** Creates a configuration object used to create a [[LanguageServerBootLoader]].
|
/** Creates a configuration object used to create a [[LanguageServerBootLoader]].
|
||||||
*
|
*
|
||||||
|
* @param bootProgressTracker an [[ActorRef]] that will get progress updates
|
||||||
|
* related to initializing the engine
|
||||||
* @param descriptor a LS descriptor
|
* @param descriptor a LS descriptor
|
||||||
* @param config a bootloader config
|
* @param config a bootloader config
|
||||||
|
* @param bootTimeout maximum time the server can use to boot,
|
||||||
|
* does not include the time needed to install any missing
|
||||||
|
* components
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
* @return a configuration object
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props(
|
def props(
|
||||||
|
bootProgressTracker: ActorRef,
|
||||||
descriptor: LanguageServerDescriptor,
|
descriptor: LanguageServerDescriptor,
|
||||||
config: BootloaderConfig
|
config: BootloaderConfig,
|
||||||
|
bootTimeout: FiniteDuration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
): Props =
|
): Props =
|
||||||
Props(new LanguageServerBootLoader(descriptor, config))
|
Props(
|
||||||
|
new LanguageServerBootLoader(
|
||||||
/** Find free socket command.
|
bootProgressTracker,
|
||||||
*/
|
descriptor,
|
||||||
case object FindFreeSocket
|
config,
|
||||||
|
bootTimeout,
|
||||||
/** Boot command.
|
executor: LanguageServerExecutor
|
||||||
*/
|
)
|
||||||
case object Boot
|
)
|
||||||
|
|
||||||
/** Signals that server boot failed.
|
/** Signals that server boot failed.
|
||||||
*
|
*
|
||||||
@ -139,12 +309,16 @@ object LanguageServerBootLoader {
|
|||||||
|
|
||||||
/** Signals that server booted successfully.
|
/** Signals that server booted successfully.
|
||||||
*
|
*
|
||||||
* @param config a server config
|
* @param connectionInfo a server config
|
||||||
* @param server a server lifecycle component
|
* @param serverProcessManager an actor that manages the server process
|
||||||
|
* lifecycle, currently it is
|
||||||
|
* [[LanguageServerBootLoader]]
|
||||||
*/
|
*/
|
||||||
case class ServerBooted(
|
case class ServerBooted(
|
||||||
config: LanguageServerConfig,
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
server: LanguageServerComponent
|
serverProcessManager: ActorRef
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case class ServerTerminated(exitCode: Int)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
|
||||||
|
/** Describes how to connect to a Language Server instance by providing its
|
||||||
|
* interface and selected ports.
|
||||||
|
*/
|
||||||
|
case class LanguageServerConnectionInfo(
|
||||||
|
interface: String,
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int
|
||||||
|
)
|
@ -2,7 +2,6 @@ package org.enso.projectmanager.infrastructure.languageserver
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import akka.actor.Status.Failure
|
|
||||||
import akka.actor.{
|
import akka.actor.{
|
||||||
Actor,
|
Actor,
|
||||||
ActorLogging,
|
ActorLogging,
|
||||||
@ -14,12 +13,7 @@ import akka.actor.{
|
|||||||
SupervisorStrategy,
|
SupervisorStrategy,
|
||||||
Terminated
|
Terminated
|
||||||
}
|
}
|
||||||
import akka.pattern.pipe
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.languageserver.boot.LifecycleComponent.ComponentStopped
|
|
||||||
import org.enso.languageserver.boot.{
|
|
||||||
LanguageServerComponent,
|
|
||||||
LanguageServerConfig
|
|
||||||
}
|
|
||||||
import org.enso.projectmanager.boot.configuration.{
|
import org.enso.projectmanager.boot.configuration.{
|
||||||
BootloaderConfig,
|
BootloaderConfig,
|
||||||
NetworkConfig,
|
NetworkConfig,
|
||||||
@ -34,35 +28,38 @@ import org.enso.projectmanager.infrastructure.languageserver.LanguageServerBootL
|
|||||||
ServerBootFailed,
|
ServerBootFailed,
|
||||||
ServerBooted
|
ServerBooted
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerController.{
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerController._
|
||||||
Boot,
|
|
||||||
BootTimeout,
|
|
||||||
ServerDied,
|
|
||||||
ShutDownServer,
|
|
||||||
ShutdownTimeout
|
|
||||||
}
|
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol._
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol._
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerRegistry.ServerShutDown
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerRegistry.ServerShutDown
|
||||||
import org.enso.projectmanager.model.Project
|
import org.enso.projectmanager.model.Project
|
||||||
import org.enso.projectmanager.util.UnhandledLogging
|
import org.enso.projectmanager.util.UnhandledLogging
|
||||||
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
/** A language server controller responsible for managing the server lifecycle.
|
/** A language server controller responsible for managing the server lifecycle.
|
||||||
* It delegates all tasks to other actors like bootloader or supervisor.
|
* It delegates all tasks to other actors like bootloader or supervisor.
|
||||||
*
|
*
|
||||||
* @param project a project open by the server
|
* @param project a project open by the server
|
||||||
|
* @param engineVersion engine version to use for the language server
|
||||||
|
* @param bootProgressTracker an [[ActorRef]] that will get progress updates
|
||||||
|
* related to initializing the engine
|
||||||
* @param networkConfig a net config
|
* @param networkConfig a net config
|
||||||
* @param bootloaderConfig a bootloader config
|
* @param bootloaderConfig a bootloader config
|
||||||
* @param supervisionConfig a supervision config
|
* @param supervisionConfig a supervision config
|
||||||
* @param timeoutConfig a timeout config
|
* @param timeoutConfig a timeout config
|
||||||
|
* @param distributionConfiguration configuration of the distribution
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
*/
|
*/
|
||||||
class LanguageServerController(
|
class LanguageServerController(
|
||||||
project: Project,
|
project: Project,
|
||||||
|
engineVersion: SemVer,
|
||||||
|
bootProgressTracker: ActorRef,
|
||||||
networkConfig: NetworkConfig,
|
networkConfig: NetworkConfig,
|
||||||
bootloaderConfig: BootloaderConfig,
|
bootloaderConfig: BootloaderConfig,
|
||||||
supervisionConfig: SupervisionConfig,
|
supervisionConfig: SupervisionConfig,
|
||||||
timeoutConfig: TimeoutConfig
|
timeoutConfig: TimeoutConfig,
|
||||||
|
distributionConfiguration: DistributionConfiguration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
) extends Actor
|
) extends Actor
|
||||||
with ActorLogging
|
with ActorLogging
|
||||||
with Stash
|
with Stash
|
||||||
@ -72,10 +69,14 @@ class LanguageServerController(
|
|||||||
|
|
||||||
private val descriptor =
|
private val descriptor =
|
||||||
LanguageServerDescriptor(
|
LanguageServerDescriptor(
|
||||||
name = s"language-server-${project.id}",
|
name = s"language-server-${project.id}",
|
||||||
rootId = UUID.randomUUID(),
|
rootId = UUID.randomUUID(),
|
||||||
root = project.path.get,
|
rootPath = project.path.get,
|
||||||
networkConfig = networkConfig
|
networkConfig = networkConfig,
|
||||||
|
distributionConfiguration = distributionConfiguration,
|
||||||
|
engineVersion = engineVersion,
|
||||||
|
jvmSettings = distributionConfiguration.defaultJVMSettings,
|
||||||
|
discardOutput = distributionConfiguration.shouldDiscardChildOutput
|
||||||
)
|
)
|
||||||
|
|
||||||
override def supervisorStrategy: SupervisorStrategy =
|
override def supervisorStrategy: SupervisorStrategy =
|
||||||
@ -92,21 +93,23 @@ class LanguageServerController(
|
|||||||
case Boot =>
|
case Boot =>
|
||||||
val bootloader =
|
val bootloader =
|
||||||
context.actorOf(
|
context.actorOf(
|
||||||
LanguageServerBootLoader.props(descriptor, bootloaderConfig),
|
LanguageServerBootLoader
|
||||||
"bootloader"
|
.props(
|
||||||
|
bootProgressTracker,
|
||||||
|
descriptor,
|
||||||
|
bootloaderConfig,
|
||||||
|
timeoutConfig.bootTimeout,
|
||||||
|
executor
|
||||||
|
),
|
||||||
|
s"bootloader-${descriptor.name}"
|
||||||
)
|
)
|
||||||
context.watch(bootloader)
|
context.watch(bootloader)
|
||||||
val timeoutCancellable =
|
context.become(booting(bootloader))
|
||||||
context.system.scheduler.scheduleOnce(30.seconds, self, BootTimeout)
|
|
||||||
context.become(booting(bootloader, timeoutCancellable))
|
|
||||||
|
|
||||||
case _ => stash()
|
case _ => stash()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def booting(
|
private def booting(Bootloader: ActorRef): Receive = {
|
||||||
Bootloader: ActorRef,
|
|
||||||
timeoutCancellable: Cancellable
|
|
||||||
): Receive = {
|
|
||||||
case BootTimeout =>
|
case BootTimeout =>
|
||||||
log.error(s"Booting failed for $descriptor")
|
log.error(s"Booting failed for $descriptor")
|
||||||
unstashAll()
|
unstashAll()
|
||||||
@ -115,28 +118,25 @@ class LanguageServerController(
|
|||||||
case ServerBootFailed(th) =>
|
case ServerBootFailed(th) =>
|
||||||
log.error(th, s"Booting failed for $descriptor")
|
log.error(th, s"Booting failed for $descriptor")
|
||||||
unstashAll()
|
unstashAll()
|
||||||
timeoutCancellable.cancel()
|
|
||||||
context.become(bootFailed(LanguageServerProtocol.ServerBootFailed(th)))
|
context.become(bootFailed(LanguageServerProtocol.ServerBootFailed(th)))
|
||||||
|
|
||||||
case ServerBooted(config, server) =>
|
case ServerBooted(connectionInfo, serverProcessManager) =>
|
||||||
unstashAll()
|
unstashAll()
|
||||||
timeoutCancellable.cancel()
|
context.become(supervising(connectionInfo, serverProcessManager))
|
||||||
context.become(supervising(config, server))
|
|
||||||
context.actorOf(
|
context.actorOf(
|
||||||
LanguageServerSupervisor.props(
|
LanguageServerSupervisor.props(
|
||||||
config,
|
connectionInfo,
|
||||||
server,
|
serverProcessManager,
|
||||||
supervisionConfig,
|
supervisionConfig,
|
||||||
new AkkaBasedWebSocketConnectionFactory(),
|
new AkkaBasedWebSocketConnectionFactory(),
|
||||||
context.system.scheduler
|
context.system.scheduler
|
||||||
),
|
),
|
||||||
"supervisor"
|
s"supervisor-${descriptor.name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
case Terminated(Bootloader) =>
|
case Terminated(Bootloader) =>
|
||||||
log.error(s"Bootloader for project ${project.name} failed")
|
log.error(s"Bootloader for project ${project.name} failed")
|
||||||
unstashAll()
|
unstashAll()
|
||||||
timeoutCancellable.cancel()
|
|
||||||
context.become(
|
context.become(
|
||||||
bootFailed(
|
bootFailed(
|
||||||
LanguageServerProtocol.ServerBootFailed(
|
LanguageServerProtocol.ServerBootFailed(
|
||||||
@ -149,33 +149,57 @@ class LanguageServerController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def supervising(
|
private def supervising(
|
||||||
config: LanguageServerConfig,
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
server: LanguageServerComponent,
|
serverProcessManager: ActorRef,
|
||||||
clients: Set[UUID] = Set.empty
|
clients: Set[UUID] = Set.empty
|
||||||
): Receive = {
|
): Receive = {
|
||||||
case StartServer(clientId, _) =>
|
case StartServer(clientId, _, requestedEngineVersion, _) =>
|
||||||
sender() ! ServerStarted(
|
if (requestedEngineVersion != engineVersion) {
|
||||||
LanguageServerSockets(
|
sender() ! ServerBootFailed(
|
||||||
Socket(config.interface, config.rpcPort),
|
new IllegalStateException(
|
||||||
Socket(config.interface, config.dataPort)
|
s"Requested to boot a server version $requestedEngineVersion, " +
|
||||||
|
s"but a server for this project with a different version, " +
|
||||||
|
s"$engineVersion, is already running. Two servers with different " +
|
||||||
|
s"versions cannot be running for a single project."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
} else {
|
||||||
context.become(supervising(config, server, clients + clientId))
|
sender() ! ServerStarted(
|
||||||
|
LanguageServerSockets(
|
||||||
|
Socket(connectionInfo.interface, connectionInfo.rpcPort),
|
||||||
|
Socket(connectionInfo.interface, connectionInfo.dataPort)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
context.become(
|
||||||
|
supervising(connectionInfo, serverProcessManager, clients + clientId)
|
||||||
|
)
|
||||||
|
}
|
||||||
case Terminated(_) =>
|
case Terminated(_) =>
|
||||||
log.debug(s"Bootloader for $project terminated.")
|
log.debug(s"Bootloader for $project terminated.")
|
||||||
|
|
||||||
case StopServer(clientId, _) =>
|
case StopServer(clientId, _) =>
|
||||||
removeClient(config, server, clients, clientId, Some(sender()))
|
removeClient(
|
||||||
|
connectionInfo,
|
||||||
|
serverProcessManager,
|
||||||
|
clients,
|
||||||
|
clientId,
|
||||||
|
Some(sender())
|
||||||
|
)
|
||||||
|
|
||||||
case ShutDownServer =>
|
case ShutDownServer =>
|
||||||
shutDownServer(server, None)
|
shutDownServer(None)
|
||||||
|
|
||||||
case ClientDisconnected(clientId) =>
|
case ClientDisconnected(clientId) =>
|
||||||
removeClient(config, server, clients, clientId, None)
|
removeClient(
|
||||||
|
connectionInfo,
|
||||||
|
serverProcessManager,
|
||||||
|
clients,
|
||||||
|
clientId,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
case RenameProject(_, oldName, newName) =>
|
case RenameProject(_, oldName, newName) =>
|
||||||
val socket = Socket(config.interface, config.rpcPort)
|
val socket = Socket(connectionInfo.interface, connectionInfo.rpcPort)
|
||||||
context.actorOf(
|
context.actorOf(
|
||||||
ProjectRenameAction
|
ProjectRenameAction
|
||||||
.props(
|
.props(
|
||||||
@ -191,33 +215,31 @@ class LanguageServerController(
|
|||||||
)
|
)
|
||||||
|
|
||||||
case ServerDied =>
|
case ServerDied =>
|
||||||
log.error(s"Language server died [$config]")
|
log.error(s"Language server died [$connectionInfo]")
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def removeClient(
|
private def removeClient(
|
||||||
config: LanguageServerConfig,
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
server: LanguageServerComponent,
|
serverProcessManager: ActorRef,
|
||||||
clients: Set[UUID],
|
clients: Set[UUID],
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
maybeRequester: Option[ActorRef]
|
maybeRequester: Option[ActorRef]
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val updatedClients = clients - clientId
|
val updatedClients = clients - clientId
|
||||||
if (updatedClients.isEmpty) {
|
if (updatedClients.isEmpty) {
|
||||||
shutDownServer(server, maybeRequester)
|
shutDownServer(maybeRequester)
|
||||||
} else {
|
} else {
|
||||||
sender() ! CannotDisconnectOtherClients
|
sender() ! CannotDisconnectOtherClients
|
||||||
context.become(supervising(config, server, updatedClients))
|
context.become(
|
||||||
|
supervising(connectionInfo, serverProcessManager, updatedClients)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def shutDownServer(
|
private def shutDownServer(maybeRequester: Option[ActorRef]): Unit = {
|
||||||
server: LanguageServerComponent,
|
|
||||||
maybeRequester: Option[ActorRef]
|
|
||||||
): Unit = {
|
|
||||||
log.debug(s"Shutting down a language server for project ${project.id}")
|
log.debug(s"Shutting down a language server for project ${project.id}")
|
||||||
context.children.foreach(_ ! GracefulStop)
|
context.children.foreach(_ ! GracefulStop)
|
||||||
server.stop() pipeTo self
|
|
||||||
val cancellable =
|
val cancellable =
|
||||||
context.system.scheduler
|
context.system.scheduler
|
||||||
.scheduleOnce(timeoutConfig.shutdownTimeout, self, ShutdownTimeout)
|
.scheduleOnce(timeoutConfig.shutdownTimeout, self, ShutdownTimeout)
|
||||||
@ -225,7 +247,7 @@ class LanguageServerController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def bootFailed(failure: ServerStartupFailure): Receive = {
|
private def bootFailed(failure: ServerStartupFailure): Receive = {
|
||||||
case StartServer(_, _) =>
|
case StartServer(_, _, _, _) =>
|
||||||
sender() ! failure
|
sender() ! failure
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
@ -234,18 +256,16 @@ class LanguageServerController(
|
|||||||
cancellable: Cancellable,
|
cancellable: Cancellable,
|
||||||
maybeRequester: Option[ActorRef]
|
maybeRequester: Option[ActorRef]
|
||||||
): Receive = {
|
): Receive = {
|
||||||
case Failure(th) =>
|
case LanguageServerProcess.ServerTerminated(exitCode) =>
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
log.error(
|
if (exitCode == 0) {
|
||||||
th,
|
log.info(s"Language server shut down successfully [$project].")
|
||||||
s"An error occurred during Language server shutdown [$project]."
|
} else {
|
||||||
)
|
log.warning(
|
||||||
maybeRequester.foreach(_ ! FailureDuringShutdown(th))
|
s"Language server shut down with non-zero exit code: $exitCode " +
|
||||||
stop()
|
s"[$project]."
|
||||||
|
)
|
||||||
case ComponentStopped =>
|
}
|
||||||
cancellable.cancel()
|
|
||||||
log.info(s"Language server shut down successfully [$project].")
|
|
||||||
maybeRequester.foreach(_ ! ServerStopped)
|
maybeRequester.foreach(_ ! ServerStopped)
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
@ -254,7 +274,7 @@ class LanguageServerController(
|
|||||||
maybeRequester.foreach(_ ! ServerShutdownTimedOut)
|
maybeRequester.foreach(_ ! ServerShutdownTimedOut)
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
case StartServer(_, _) =>
|
case StartServer(_, _, _, _) =>
|
||||||
sender() ! PreviousInstanceNotShutDown
|
sender() ! PreviousInstanceNotShutDown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,26 +303,40 @@ object LanguageServerController {
|
|||||||
/** Creates a configuration object used to create a [[LanguageServerController]].
|
/** Creates a configuration object used to create a [[LanguageServerController]].
|
||||||
*
|
*
|
||||||
* @param project a project open by the server
|
* @param project a project open by the server
|
||||||
|
* @param engineVersion engine version to use for the language server
|
||||||
|
* @param bootProgressTracker an [[ActorRef]] that will get progress updates
|
||||||
|
* related to initializing the engine
|
||||||
* @param networkConfig a net config
|
* @param networkConfig a net config
|
||||||
* @param bootloaderConfig a bootloader config
|
* @param bootloaderConfig a bootloader config
|
||||||
* @param supervisionConfig a supervision config
|
* @param supervisionConfig a supervision config
|
||||||
* @param timeoutConfig a timeout config
|
* @param timeoutConfig a timeout config
|
||||||
|
* @param distributionConfiguration configuration of the distribution
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
* @return a configuration object
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props(
|
def props(
|
||||||
project: Project,
|
project: Project,
|
||||||
|
engineVersion: SemVer,
|
||||||
|
bootProgressTracker: ActorRef,
|
||||||
networkConfig: NetworkConfig,
|
networkConfig: NetworkConfig,
|
||||||
bootloaderConfig: BootloaderConfig,
|
bootloaderConfig: BootloaderConfig,
|
||||||
supervisionConfig: SupervisionConfig,
|
supervisionConfig: SupervisionConfig,
|
||||||
timeoutConfig: TimeoutConfig
|
timeoutConfig: TimeoutConfig,
|
||||||
|
distributionConfiguration: DistributionConfiguration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
): Props =
|
): Props =
|
||||||
Props(
|
Props(
|
||||||
new LanguageServerController(
|
new LanguageServerController(
|
||||||
project,
|
project,
|
||||||
|
engineVersion,
|
||||||
|
bootProgressTracker,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
bootloaderConfig,
|
bootloaderConfig,
|
||||||
supervisionConfig,
|
supervisionConfig,
|
||||||
timeoutConfig
|
timeoutConfig,
|
||||||
|
distributionConfiguration,
|
||||||
|
executor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,18 +2,32 @@ package org.enso.projectmanager.infrastructure.languageserver
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.projectmanager.boot.configuration.NetworkConfig
|
import org.enso.projectmanager.boot.configuration.NetworkConfig
|
||||||
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
|
import org.enso.runtimeversionmanager.runner.JVMSettings
|
||||||
|
|
||||||
/** A descriptor used to start up a Language Server.
|
/** A descriptor specifying options related to starting a Language Server.
|
||||||
*
|
*
|
||||||
* @param name a name of the LS
|
* @param name a name of the LS
|
||||||
* @param rootId a content root id
|
* @param rootId a content root id
|
||||||
* @param root a path to the content root
|
* @param rootPath a path to the content root
|
||||||
* @param networkConfig a network config
|
* @param networkConfig a network config
|
||||||
|
* @param distributionConfiguration configuration of current distribution, used
|
||||||
|
* to find installed (or install new) engine
|
||||||
|
* versions
|
||||||
|
* @param engineVersion version of the langauge server's engine to use
|
||||||
|
* @param jvmSettings settings to use for the JVM that will host the engine
|
||||||
|
* @param discardOutput specifies if the process output should be discarded or
|
||||||
|
* printed to parent's streams
|
||||||
*/
|
*/
|
||||||
case class LanguageServerDescriptor(
|
case class LanguageServerDescriptor(
|
||||||
name: String,
|
name: String,
|
||||||
rootId: UUID,
|
rootId: UUID,
|
||||||
root: String,
|
rootPath: String,
|
||||||
networkConfig: NetworkConfig
|
networkConfig: NetworkConfig,
|
||||||
|
distributionConfiguration: DistributionConfiguration,
|
||||||
|
engineVersion: SemVer,
|
||||||
|
jvmSettings: JVMSettings,
|
||||||
|
discardOutput: Boolean
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerExecutor.LifecycleListener
|
||||||
|
|
||||||
|
/** A service responsible for executing the forked language server process. */
|
||||||
|
trait LanguageServerExecutor {
|
||||||
|
|
||||||
|
/** Starts the language server process in a background thread.
|
||||||
|
*
|
||||||
|
* @param descriptor options related to this language server instance
|
||||||
|
* @param progressTracker reference to an actor that should be notifed of any
|
||||||
|
* locks
|
||||||
|
* @param rpcPort port to use for the RPC channel
|
||||||
|
* @param dataPort port to use for the binary channel
|
||||||
|
* @param lifecycleListener a listener that will be notified when the process
|
||||||
|
* is started and terminated
|
||||||
|
*/
|
||||||
|
def spawn(
|
||||||
|
descriptor: LanguageServerDescriptor,
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
lifecycleListener: LifecycleListener
|
||||||
|
): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
object LanguageServerExecutor {
|
||||||
|
|
||||||
|
/** Listens for lifecycle updates of a language server process. */
|
||||||
|
trait LifecycleListener {
|
||||||
|
|
||||||
|
/** Called when the process has been successfully started.
|
||||||
|
*
|
||||||
|
* @param processHandle a handle that can be used to terminate the process
|
||||||
|
*/
|
||||||
|
def onStarted(processHandle: ProcessHandle): Unit
|
||||||
|
|
||||||
|
/** Called when the process has terminated (either on request or abruptly).
|
||||||
|
*
|
||||||
|
* @param exitCode exit code of the child process
|
||||||
|
*/
|
||||||
|
def onTerminated(exitCode: Int): Unit
|
||||||
|
|
||||||
|
/** Called when the process fails to start or its execution terminates with
|
||||||
|
* an unexpected exception.
|
||||||
|
*/
|
||||||
|
def onFailed(throwable: Throwable): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A handle to the running Language Server child process. */
|
||||||
|
trait ProcessHandle {
|
||||||
|
|
||||||
|
/** Requests the child process to terminate gracefully. */
|
||||||
|
def requestGracefulTermination(): Unit
|
||||||
|
|
||||||
|
/** Tries to forcibly kill the process. */
|
||||||
|
def kill(): Unit
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ package org.enso.projectmanager.infrastructure.languageserver
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.projectmanager.data.LanguageServerSockets
|
import org.enso.projectmanager.data.LanguageServerSockets
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol.{
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol.{
|
||||||
CheckTimeout,
|
CheckTimeout,
|
||||||
@ -20,13 +22,20 @@ trait LanguageServerGateway[F[+_, +_]] {
|
|||||||
|
|
||||||
/** Starts a language server.
|
/** Starts a language server.
|
||||||
*
|
*
|
||||||
|
* It assumes that the required engine version has been preinstalled.
|
||||||
|
*
|
||||||
|
* @param progressTracker an ActorRef that should get notifications when
|
||||||
|
* waiting on a lock
|
||||||
* @param clientId a requester id
|
* @param clientId a requester id
|
||||||
* @param project a project to start
|
* @param project a project to start
|
||||||
|
* @param version engine version to use for the launched language server
|
||||||
* @return either a failure or sockets that a language server listens on
|
* @return either a failure or sockets that a language server listens on
|
||||||
*/
|
*/
|
||||||
def start(
|
def start(
|
||||||
|
progressTracker: ActorRef,
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
project: Project
|
project: Project,
|
||||||
|
version: SemVer
|
||||||
): F[ServerStartupFailure, LanguageServerSockets]
|
): F[ServerStartupFailure, LanguageServerSockets]
|
||||||
|
|
||||||
/** Stops a lang. server.
|
/** Stops a lang. server.
|
||||||
|
@ -5,6 +5,7 @@ import java.util.UUID
|
|||||||
import akka.actor.{ActorRef, ActorSystem}
|
import akka.actor.{ActorRef, ActorSystem}
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
||||||
import org.enso.projectmanager.control.core.CovariantFlatMap
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
import org.enso.projectmanager.control.core.syntax._
|
import org.enso.projectmanager.control.core.syntax._
|
||||||
@ -38,14 +39,23 @@ class LanguageServerGatewayImpl[
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def start(
|
override def start(
|
||||||
|
progressTracker: ActorRef,
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
project: Project
|
project: Project,
|
||||||
|
version: SemVer
|
||||||
): F[ServerStartupFailure, LanguageServerSockets] = {
|
): F[ServerStartupFailure, LanguageServerSockets] = {
|
||||||
implicit val timeout: Timeout = Timeout(timeoutConfig.bootTimeout)
|
implicit val timeout: Timeout = Timeout(2 * timeoutConfig.bootTimeout)
|
||||||
|
|
||||||
|
// TODO [RW] this can timeout if the boot is stuck waiting on a lock, how do
|
||||||
|
// we want to handle that? #1315
|
||||||
Async[F]
|
Async[F]
|
||||||
.fromFuture { () =>
|
.fromFuture { () =>
|
||||||
(registry ? StartServer(clientId, project)).mapTo[ServerStartupResult]
|
(registry ? StartServer(
|
||||||
|
clientId,
|
||||||
|
project,
|
||||||
|
version,
|
||||||
|
progressTracker
|
||||||
|
)).mapTo[ServerStartupResult]
|
||||||
}
|
}
|
||||||
.mapError(_ => ServerBootTimedOut)
|
.mapError(_ => ServerBootTimedOut)
|
||||||
.flatMap {
|
.flatMap {
|
||||||
|
@ -0,0 +1,235 @@
|
|||||||
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash}
|
||||||
|
import org.enso.projectmanager.data.Socket
|
||||||
|
import org.enso.projectmanager.infrastructure.http.AkkaBasedWebSocketConnectionFactory
|
||||||
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerExecutor.ProcessHandle
|
||||||
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProcess.{
|
||||||
|
Kill,
|
||||||
|
ServerTerminated,
|
||||||
|
ServerThreadFailed,
|
||||||
|
Stop
|
||||||
|
}
|
||||||
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerSupervisor.{
|
||||||
|
HeartbeatReceived,
|
||||||
|
ServerUnresponsive
|
||||||
|
}
|
||||||
|
|
||||||
|
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
||||||
|
|
||||||
|
/** An Actor that manages a single Language Server process.
|
||||||
|
*
|
||||||
|
* It starts the process upon creation and notifies the parent once the process
|
||||||
|
* has finished booting.
|
||||||
|
*
|
||||||
|
* @param progressTracker an [[ActorRef]] that will get progress updates
|
||||||
|
* related to initializing the engine
|
||||||
|
* @param descriptor a LS descriptor
|
||||||
|
* @param rpcPort port to bind for RPC connections
|
||||||
|
* @param dataPort port to bind for binary connections
|
||||||
|
* @param bootTimeout maximum time permitted to wait for the process to finish
|
||||||
|
* initializing; if the initialization heartbeat is not
|
||||||
|
* received within this time the boot is treated as failed
|
||||||
|
* and the process is gracefully stopped
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
|
*/
|
||||||
|
class LanguageServerProcess(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
descriptor: LanguageServerDescriptor,
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
bootTimeout: FiniteDuration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
|
) extends Actor
|
||||||
|
with Stash {
|
||||||
|
|
||||||
|
import context.dispatcher
|
||||||
|
|
||||||
|
override def preStart(): Unit = {
|
||||||
|
super.preStart()
|
||||||
|
self ! Boot
|
||||||
|
}
|
||||||
|
|
||||||
|
override def receive: Receive = initializationStage
|
||||||
|
|
||||||
|
object LifecycleListener extends LanguageServerExecutor.LifecycleListener {
|
||||||
|
override def onStarted(processHandle: ProcessHandle): Unit =
|
||||||
|
self ! ProcessStarted(processHandle)
|
||||||
|
|
||||||
|
override def onTerminated(exitCode: Int): Unit =
|
||||||
|
self ! ProcessTerminated(exitCode)
|
||||||
|
|
||||||
|
override def onFailed(throwable: Throwable): Unit =
|
||||||
|
self ! ProcessFailed(throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** First stage, it launches the child process and sets up the futures to send
|
||||||
|
* back the notifications and goes to the startingStage.
|
||||||
|
*/
|
||||||
|
private def initializationStage: Receive = {
|
||||||
|
case Boot =>
|
||||||
|
executor.spawn(
|
||||||
|
descriptor = descriptor,
|
||||||
|
progressTracker = progressTracker,
|
||||||
|
rpcPort = rpcPort,
|
||||||
|
dataPort = dataPort,
|
||||||
|
lifecycleListener = LifecycleListener
|
||||||
|
)
|
||||||
|
context.become(startingStage)
|
||||||
|
case _ => stash()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Waits for the process to actually start (this may take a long time if
|
||||||
|
* there are locked locks).
|
||||||
|
*
|
||||||
|
* Once the process is started, it goes to the bootingStage.
|
||||||
|
*/
|
||||||
|
private def startingStage: Receive = {
|
||||||
|
case ProcessStarted(process) =>
|
||||||
|
val cancellable =
|
||||||
|
context.system.scheduler.scheduleOnce(bootTimeout, self, TimedOut)
|
||||||
|
context.become(bootingStage(process, cancellable))
|
||||||
|
unstashAll()
|
||||||
|
self ! AskServerIfStarted
|
||||||
|
case ProcessFailed(error) => handleFatalError(error)
|
||||||
|
case _ => stash()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def handleFatalError(error: Throwable): Unit = {
|
||||||
|
context.parent ! ServerThreadFailed(error)
|
||||||
|
context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** In booting stage, the actor is retrying to connect to the server to verify
|
||||||
|
* that it has finished initialization.
|
||||||
|
*
|
||||||
|
* Once initialization is confirmed, it notifies the parent and proceeds to
|
||||||
|
* runningStage.
|
||||||
|
*
|
||||||
|
* Before initialization is confirmed the actor can also:
|
||||||
|
* - trigger the timeout or receive a graceful stop message which will ask
|
||||||
|
* the child process to terminate gracefully,
|
||||||
|
* - get a notification that the child process has terminated spuriously
|
||||||
|
* (possibly a crash).
|
||||||
|
*/
|
||||||
|
private def bootingStage(
|
||||||
|
process: ProcessHandle,
|
||||||
|
bootTimeout: Cancellable
|
||||||
|
): Receive =
|
||||||
|
handleBootResponse(process, bootTimeout).orElse(runningStage(process))
|
||||||
|
|
||||||
|
case object AskServerIfStarted
|
||||||
|
case object TimedOut
|
||||||
|
private val retryDelay = 100.milliseconds
|
||||||
|
|
||||||
|
private def handleBootResponse(
|
||||||
|
process: ProcessHandle,
|
||||||
|
bootTimeout: Cancellable
|
||||||
|
): Receive = {
|
||||||
|
case AskServerIfStarted =>
|
||||||
|
val socket = Socket(descriptor.networkConfig.interface, rpcPort)
|
||||||
|
context.actorOf(
|
||||||
|
HeartbeatSession.initialProps(
|
||||||
|
socket,
|
||||||
|
retryDelay,
|
||||||
|
new AkkaBasedWebSocketConnectionFactory()(context.system),
|
||||||
|
context.system.scheduler
|
||||||
|
),
|
||||||
|
s"initial-heartbeat-${UUID.randomUUID()}"
|
||||||
|
)
|
||||||
|
case TimedOut =>
|
||||||
|
self ! Stop
|
||||||
|
case HeartbeatReceived =>
|
||||||
|
context.parent ! LanguageServerProcess.ServerConfirmedFinishedBooting
|
||||||
|
bootTimeout.cancel()
|
||||||
|
context.become(runningStage(process))
|
||||||
|
case ServerUnresponsive =>
|
||||||
|
import context.dispatcher
|
||||||
|
context.system.scheduler.scheduleOnce(
|
||||||
|
retryDelay,
|
||||||
|
self,
|
||||||
|
AskServerIfStarted
|
||||||
|
)
|
||||||
|
context.become(bootingStage(process, bootTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** When the process is running, the actor is monitoring it and managing its
|
||||||
|
* lifetime.
|
||||||
|
*
|
||||||
|
* If termination is requested, the process will be asked to gracefully
|
||||||
|
* terminate or be killed. If the process terminates (either on request or
|
||||||
|
* spuriously, e.g. by a crash), the parent is notified and the current actor
|
||||||
|
* is stopped.
|
||||||
|
*/
|
||||||
|
private def runningStage(process: ProcessHandle): Receive = {
|
||||||
|
case Stop => process.requestGracefulTermination()
|
||||||
|
case Kill => process.kill()
|
||||||
|
case ProcessTerminated(exitCode) =>
|
||||||
|
context.parent ! ServerTerminated(exitCode)
|
||||||
|
context.stop(self)
|
||||||
|
case ProcessFailed(error) => handleFatalError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
private case object Boot
|
||||||
|
private case class ProcessStarted(process: ProcessHandle)
|
||||||
|
private case class ProcessTerminated(exitCode: Int)
|
||||||
|
private case class ProcessFailed(throwable: Throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
object LanguageServerProcess {
|
||||||
|
|
||||||
|
/** Creates a configuration object used to create a [[LanguageServerProcess]].
|
||||||
|
*
|
||||||
|
* @param progressTracker an [[ActorRef]] that will get progress updates
|
||||||
|
* related to initializing the engine
|
||||||
|
* @param descriptor a LS descriptor
|
||||||
|
* @param rpcPort port to bind for RPC connections
|
||||||
|
* @param dataPort port to bind for binary connections
|
||||||
|
* @param bootTimeout maximum time permitted to wait for the process to finish
|
||||||
|
* initializing; if the initialization heartbeat is not
|
||||||
|
* received within this time the boot is treated as failed
|
||||||
|
* and the process is gracefully stopped
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
|
* @return a configuration object
|
||||||
|
*/
|
||||||
|
def props(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
descriptor: LanguageServerDescriptor,
|
||||||
|
rpcPort: Int,
|
||||||
|
dataPort: Int,
|
||||||
|
bootTimeout: FiniteDuration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
|
): Props = Props(
|
||||||
|
new LanguageServerProcess(
|
||||||
|
progressTracker,
|
||||||
|
descriptor,
|
||||||
|
rpcPort,
|
||||||
|
dataPort,
|
||||||
|
bootTimeout,
|
||||||
|
executor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Sent to the parent when the server has terminated (for any reason: on
|
||||||
|
* request or on its own, e.g. due to a signal or crash).
|
||||||
|
*/
|
||||||
|
case class ServerTerminated(exitCode: Int)
|
||||||
|
|
||||||
|
/** Sent to the parent when starting the server has failed with an exception.
|
||||||
|
*/
|
||||||
|
case class ServerThreadFailed(throwable: Throwable)
|
||||||
|
|
||||||
|
/** Sent to the parent when the child process has confirmed that it is fully
|
||||||
|
* initialized.
|
||||||
|
*/
|
||||||
|
case object ServerConfirmedFinishedBooting
|
||||||
|
|
||||||
|
/** Sent to forcibly kill the server. */
|
||||||
|
case object Kill
|
||||||
|
|
||||||
|
/** Sent to gracefully request to stop the server. */
|
||||||
|
case object Stop
|
||||||
|
}
|
@ -2,6 +2,8 @@ package org.enso.projectmanager.infrastructure.languageserver
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.projectmanager.data.LanguageServerSockets
|
import org.enso.projectmanager.data.LanguageServerSockets
|
||||||
import org.enso.projectmanager.model.Project
|
import org.enso.projectmanager.model.Project
|
||||||
|
|
||||||
@ -13,8 +15,16 @@ object LanguageServerProtocol {
|
|||||||
*
|
*
|
||||||
* @param clientId the requester id
|
* @param clientId the requester id
|
||||||
* @param project the project to start
|
* @param project the project to start
|
||||||
|
* @param engineVersion version of the engine to use
|
||||||
|
* @param progressTracker an actor that should be sent notifications about
|
||||||
|
* locks
|
||||||
*/
|
*/
|
||||||
case class StartServer(clientId: UUID, project: Project)
|
case class StartServer(
|
||||||
|
clientId: UUID,
|
||||||
|
project: Project,
|
||||||
|
engineVersion: SemVer,
|
||||||
|
progressTracker: ActorRef
|
||||||
|
)
|
||||||
|
|
||||||
/** Base trait for server startup results.
|
/** Base trait for server startup results.
|
||||||
*/
|
*/
|
||||||
|
@ -20,21 +20,27 @@ import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProto
|
|||||||
}
|
}
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerRegistry.ServerShutDown
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerRegistry.ServerShutDown
|
||||||
import org.enso.projectmanager.util.UnhandledLogging
|
import org.enso.projectmanager.util.UnhandledLogging
|
||||||
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
|
|
||||||
/** An actor that routes request regarding lang. server lifecycle to the
|
/** An actor that routes request regarding lang. server lifecycle to the
|
||||||
* right controller that manages the server.
|
* right controller that manages the server.
|
||||||
* It creates a controller actor, if a server doesn't exists.
|
* It creates a controller actor, if a server doesn't exist.
|
||||||
*
|
*
|
||||||
* @param networkConfig a net config
|
* @param networkConfig a net config
|
||||||
* @param bootloaderConfig a bootloader config
|
* @param bootloaderConfig a bootloader config
|
||||||
* @param supervisionConfig a supervision config
|
* @param supervisionConfig a supervision config
|
||||||
* @param timeoutConfig a timeout config
|
* @param timeoutConfig a timeout config
|
||||||
|
* @param distributionConfiguration configuration of the distribution
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
*/
|
*/
|
||||||
class LanguageServerRegistry(
|
class LanguageServerRegistry(
|
||||||
networkConfig: NetworkConfig,
|
networkConfig: NetworkConfig,
|
||||||
bootloaderConfig: BootloaderConfig,
|
bootloaderConfig: BootloaderConfig,
|
||||||
supervisionConfig: SupervisionConfig,
|
supervisionConfig: SupervisionConfig,
|
||||||
timeoutConfig: TimeoutConfig
|
timeoutConfig: TimeoutConfig,
|
||||||
|
distributionConfiguration: DistributionConfiguration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
) extends Actor
|
) extends Actor
|
||||||
with ActorLogging
|
with ActorLogging
|
||||||
with UnhandledLogging {
|
with UnhandledLogging {
|
||||||
@ -44,7 +50,7 @@ class LanguageServerRegistry(
|
|||||||
private def running(
|
private def running(
|
||||||
serverControllers: Map[UUID, ActorRef] = Map.empty
|
serverControllers: Map[UUID, ActorRef] = Map.empty
|
||||||
): Receive = {
|
): Receive = {
|
||||||
case msg @ StartServer(_, project) =>
|
case msg @ StartServer(_, project, engineVersion, progressTracker) =>
|
||||||
if (serverControllers.contains(project.id)) {
|
if (serverControllers.contains(project.id)) {
|
||||||
serverControllers(project.id).forward(msg)
|
serverControllers(project.id).forward(msg)
|
||||||
} else {
|
} else {
|
||||||
@ -52,10 +58,14 @@ class LanguageServerRegistry(
|
|||||||
LanguageServerController
|
LanguageServerController
|
||||||
.props(
|
.props(
|
||||||
project,
|
project,
|
||||||
|
engineVersion,
|
||||||
|
progressTracker,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
bootloaderConfig,
|
bootloaderConfig,
|
||||||
supervisionConfig,
|
supervisionConfig,
|
||||||
timeoutConfig
|
timeoutConfig,
|
||||||
|
distributionConfiguration,
|
||||||
|
executor
|
||||||
),
|
),
|
||||||
s"language-server-controller-${project.id}"
|
s"language-server-controller-${project.id}"
|
||||||
)
|
)
|
||||||
@ -116,20 +126,27 @@ object LanguageServerRegistry {
|
|||||||
* @param bootloaderConfig a bootloader config
|
* @param bootloaderConfig a bootloader config
|
||||||
* @param supervisionConfig a supervision config
|
* @param supervisionConfig a supervision config
|
||||||
* @param timeoutConfig a timeout config
|
* @param timeoutConfig a timeout config
|
||||||
* @return
|
* @param distributionConfiguration configuration of the distribution
|
||||||
|
* @param executor an executor service used to start the language server
|
||||||
|
* process
|
||||||
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props(
|
def props(
|
||||||
networkConfig: NetworkConfig,
|
networkConfig: NetworkConfig,
|
||||||
bootloaderConfig: BootloaderConfig,
|
bootloaderConfig: BootloaderConfig,
|
||||||
supervisionConfig: SupervisionConfig,
|
supervisionConfig: SupervisionConfig,
|
||||||
timeoutConfig: TimeoutConfig
|
timeoutConfig: TimeoutConfig,
|
||||||
|
distributionConfiguration: DistributionConfiguration,
|
||||||
|
executor: LanguageServerExecutor
|
||||||
): Props =
|
): Props =
|
||||||
Props(
|
Props(
|
||||||
new LanguageServerRegistry(
|
new LanguageServerRegistry(
|
||||||
networkConfig,
|
networkConfig,
|
||||||
bootloaderConfig,
|
bootloaderConfig,
|
||||||
supervisionConfig,
|
supervisionConfig,
|
||||||
timeoutConfig
|
timeoutConfig,
|
||||||
|
distributionConfiguration,
|
||||||
|
executor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,24 +2,24 @@ package org.enso.projectmanager.infrastructure.languageserver
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import akka.actor.Status.Failure
|
|
||||||
import akka.actor.{
|
import akka.actor.{
|
||||||
Actor,
|
Actor,
|
||||||
ActorLogging,
|
ActorLogging,
|
||||||
|
ActorRef,
|
||||||
Cancellable,
|
Cancellable,
|
||||||
Props,
|
Props,
|
||||||
Scheduler,
|
Scheduler,
|
||||||
Terminated
|
Terminated
|
||||||
}
|
}
|
||||||
import akka.pattern.pipe
|
|
||||||
import org.enso.languageserver.boot.LifecycleComponent.ComponentRestarted
|
|
||||||
import org.enso.languageserver.boot.{LanguageServerConfig, LifecycleComponent}
|
|
||||||
import org.enso.projectmanager.boot.configuration.SupervisionConfig
|
import org.enso.projectmanager.boot.configuration.SupervisionConfig
|
||||||
import org.enso.projectmanager.data.Socket
|
import org.enso.projectmanager.data.Socket
|
||||||
import org.enso.projectmanager.infrastructure.http.WebSocketConnectionFactory
|
import org.enso.projectmanager.infrastructure.http.WebSocketConnectionFactory
|
||||||
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerBootLoader.{
|
||||||
|
ServerBootFailed,
|
||||||
|
ServerBooted
|
||||||
|
}
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerController.ServerDied
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerController.ServerDied
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerSupervisor.{
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerSupervisor.{
|
||||||
RestartServer,
|
|
||||||
SendHeartbeat,
|
SendHeartbeat,
|
||||||
ServerUnresponsive,
|
ServerUnresponsive,
|
||||||
StartSupervision
|
StartSupervision
|
||||||
@ -30,15 +30,16 @@ import org.enso.projectmanager.util.UnhandledLogging
|
|||||||
* restarting it when the server is unresponsive. It delegates server
|
* restarting it when the server is unresponsive. It delegates server
|
||||||
* monitoring to the [[HeartbeatSession]] actor.
|
* monitoring to the [[HeartbeatSession]] actor.
|
||||||
*
|
*
|
||||||
* @param config a server config
|
* @param connectionInfo a server connection info
|
||||||
* @param server a server handle
|
* @param serverProcessManager an actor that manages the lifecycle of the
|
||||||
|
* server process
|
||||||
* @param supervisionConfig a supervision config
|
* @param supervisionConfig a supervision config
|
||||||
* @param connectionFactory a web socket connection factory
|
* @param connectionFactory a web socket connection factory
|
||||||
* @param scheduler a scheduler
|
* @param scheduler a scheduler
|
||||||
*/
|
*/
|
||||||
class LanguageServerSupervisor(
|
class LanguageServerSupervisor(
|
||||||
config: LanguageServerConfig,
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
server: LifecycleComponent,
|
serverProcessManager: ActorRef,
|
||||||
supervisionConfig: SupervisionConfig,
|
supervisionConfig: SupervisionConfig,
|
||||||
connectionFactory: WebSocketConnectionFactory,
|
connectionFactory: WebSocketConnectionFactory,
|
||||||
scheduler: Scheduler
|
scheduler: Scheduler
|
||||||
@ -69,7 +70,7 @@ class LanguageServerSupervisor(
|
|||||||
|
|
||||||
private def supervising(cancellable: Cancellable): Receive = {
|
private def supervising(cancellable: Cancellable): Receive = {
|
||||||
case SendHeartbeat =>
|
case SendHeartbeat =>
|
||||||
val socket = Socket(config.interface, config.rpcPort)
|
val socket = Socket(connectionInfo.interface, connectionInfo.rpcPort)
|
||||||
context.actorOf(
|
context.actorOf(
|
||||||
HeartbeatSession.props(
|
HeartbeatSession.props(
|
||||||
socket,
|
socket,
|
||||||
@ -81,40 +82,31 @@ class LanguageServerSupervisor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
case ServerUnresponsive =>
|
case ServerUnresponsive =>
|
||||||
log.info(s"Server is unresponsive [$config]. Restarting it...")
|
log.info(s"Server is unresponsive [$connectionInfo]. Restarting it...")
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
log.info(s"Restarting first time the server")
|
log.info(s"Restarting the server")
|
||||||
server.restart() pipeTo self
|
serverProcessManager ! Restart
|
||||||
context.become(restarting())
|
context.become(restarting)
|
||||||
|
|
||||||
case GracefulStop =>
|
case GracefulStop =>
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def restarting(restartCount: Int = 1): Receive = {
|
private def restarting: Receive = {
|
||||||
case RestartServer =>
|
case ServerBootFailed(_) =>
|
||||||
log.info(s"Restarting $restartCount time the server")
|
log.error("Cannot restart language server")
|
||||||
server.restart() pipeTo self
|
context.parent ! ServerDied
|
||||||
()
|
context.stop(self)
|
||||||
|
|
||||||
case Failure(th) =>
|
case ServerBooted(_, newProcessManager) =>
|
||||||
log.error(th, s"An error occurred during restarting the server [$config]")
|
if (newProcessManager != serverProcessManager) {
|
||||||
if (restartCount < supervisionConfig.numberOfRestarts) {
|
log.error(
|
||||||
scheduler.scheduleOnce(
|
"The process manager actor has changed. This should never happen. " +
|
||||||
supervisionConfig.delayBetweenRestarts,
|
"Supervisor may no longer work correctly."
|
||||||
self,
|
|
||||||
RestartServer
|
|
||||||
)
|
)
|
||||||
context.become(restarting(restartCount + 1))
|
|
||||||
} else {
|
|
||||||
log.error("Cannot restart language server")
|
|
||||||
context.parent ! ServerDied
|
|
||||||
context.stop(self)
|
|
||||||
}
|
}
|
||||||
|
log.info(s"Language server restarted [$connectionInfo]")
|
||||||
case ComponentRestarted =>
|
|
||||||
log.info(s"Language server restarted [$config]")
|
|
||||||
val cancellable =
|
val cancellable =
|
||||||
scheduler.scheduleAtFixedRate(
|
scheduler.scheduleAtFixedRate(
|
||||||
supervisionConfig.initialDelay,
|
supervisionConfig.initialDelay,
|
||||||
@ -150,8 +142,6 @@ object LanguageServerSupervisor {
|
|||||||
|
|
||||||
private case object StartSupervision
|
private case object StartSupervision
|
||||||
|
|
||||||
private case object RestartServer
|
|
||||||
|
|
||||||
/** A command responsible for initiating heartbeat session.
|
/** A command responsible for initiating heartbeat session.
|
||||||
*/
|
*/
|
||||||
case object SendHeartbeat
|
case object SendHeartbeat
|
||||||
@ -160,26 +150,30 @@ object LanguageServerSupervisor {
|
|||||||
*/
|
*/
|
||||||
case object ServerUnresponsive
|
case object ServerUnresponsive
|
||||||
|
|
||||||
|
/** Signals that the heartbeat has been received (only sent if demanded). */
|
||||||
|
case object HeartbeatReceived
|
||||||
|
|
||||||
/** Creates a configuration object used to create a [[LanguageServerSupervisor]].
|
/** Creates a configuration object used to create a [[LanguageServerSupervisor]].
|
||||||
*
|
*
|
||||||
* @param config a server config
|
* @param connectionInfo a server config
|
||||||
* @param server a server handle
|
* @param serverProcessManager an actor that manages the lifecycle of the
|
||||||
|
* server process
|
||||||
* @param supervisionConfig a supervision config
|
* @param supervisionConfig a supervision config
|
||||||
* @param connectionFactory a web socket connection factory
|
* @param connectionFactory a web socket connection factory
|
||||||
* @param scheduler a scheduler
|
* @param scheduler a scheduler
|
||||||
* @return a configuration object
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props(
|
def props(
|
||||||
config: LanguageServerConfig,
|
connectionInfo: LanguageServerConnectionInfo,
|
||||||
server: LifecycleComponent,
|
serverProcessManager: ActorRef,
|
||||||
supervisionConfig: SupervisionConfig,
|
supervisionConfig: SupervisionConfig,
|
||||||
connectionFactory: WebSocketConnectionFactory,
|
connectionFactory: WebSocketConnectionFactory,
|
||||||
scheduler: Scheduler
|
scheduler: Scheduler
|
||||||
): Props =
|
): Props =
|
||||||
Props(
|
Props(
|
||||||
new LanguageServerSupervisor(
|
new LanguageServerSupervisor(
|
||||||
config,
|
connectionInfo,
|
||||||
server,
|
serverProcessManager,
|
||||||
supervisionConfig,
|
supervisionConfig,
|
||||||
connectionFactory,
|
connectionFactory,
|
||||||
scheduler
|
scheduler
|
||||||
|
@ -2,8 +2,9 @@ package org.enso.projectmanager.infrastructure
|
|||||||
|
|
||||||
package object languageserver {
|
package object languageserver {
|
||||||
|
|
||||||
/** A stop command.
|
/** A stop command. */
|
||||||
*/
|
|
||||||
case object GracefulStop
|
case object GracefulStop
|
||||||
|
|
||||||
|
/** Requests to restart the language server. */
|
||||||
|
case object Restart
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.enso.projectmanager.infrastructure.repository
|
package org.enso.projectmanager.infrastructure.repository
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import org.enso.pkg.{Package, PackageManager}
|
import org.enso.pkg.{Package, PackageManager}
|
||||||
@ -73,16 +74,9 @@ class ProjectFileRepository[
|
|||||||
getAll().map(_.find(_.id == projectId))
|
getAll().map(_.find(_.id == projectId))
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def create(
|
override def findPathForNewProject(
|
||||||
project: Project
|
project: Project
|
||||||
): F[ProjectRepositoryFailure, Unit] =
|
): F[ProjectRepositoryFailure, Path] = findTargetPath(project).map(_.toPath)
|
||||||
for {
|
|
||||||
projectPath <- findTargetPath(project)
|
|
||||||
_ <- createProjectStructure(project, projectPath)
|
|
||||||
_ <- metadataStorage(projectPath)
|
|
||||||
.persist(ProjectMetadata(project))
|
|
||||||
.mapError(th => StorageFailure(th.toString))
|
|
||||||
} yield ()
|
|
||||||
|
|
||||||
private def tryLoadProject(
|
private def tryLoadProject(
|
||||||
directory: File
|
directory: File
|
||||||
@ -97,12 +91,13 @@ class ProjectFileRepository[
|
|||||||
meta <- metaOpt
|
meta <- metaOpt
|
||||||
} yield {
|
} yield {
|
||||||
Project(
|
Project(
|
||||||
id = meta.id,
|
id = meta.id,
|
||||||
name = pkg.name,
|
name = pkg.name,
|
||||||
kind = meta.kind,
|
kind = meta.kind,
|
||||||
created = meta.created,
|
created = meta.created,
|
||||||
lastOpened = meta.lastOpened,
|
engineVersion = pkg.config.ensoVersion,
|
||||||
path = Some(directory.toString)
|
lastOpened = meta.lastOpened,
|
||||||
|
path = Some(directory.toString)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,14 +110,6 @@ class ProjectFileRepository[
|
|||||||
.map(Some(_))
|
.map(Some(_))
|
||||||
.mapError(_.fold(convertFileStorageFailure))
|
.mapError(_.fold(convertFileStorageFailure))
|
||||||
|
|
||||||
private def createProjectStructure(
|
|
||||||
project: Project,
|
|
||||||
projectPath: File
|
|
||||||
): F[StorageFailure, Package[File]] =
|
|
||||||
Sync[F]
|
|
||||||
.blockingOp { PackageManager.Default.create(projectPath, project.name) }
|
|
||||||
.mapError(th => StorageFailure(th.toString))
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def rename(
|
override def rename(
|
||||||
projectId: UUID,
|
projectId: UUID,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.enso.projectmanager.infrastructure.repository
|
package org.enso.projectmanager.infrastructure.repository
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import org.enso.projectmanager.model.Project
|
import org.enso.projectmanager.model.Project
|
||||||
@ -18,12 +19,15 @@ trait ProjectRepository[F[+_, +_]] {
|
|||||||
*/
|
*/
|
||||||
def exists(name: String): F[ProjectRepositoryFailure, Boolean]
|
def exists(name: String): F[ProjectRepositoryFailure, Boolean]
|
||||||
|
|
||||||
/** Creates the provided user project in the storage.
|
/** Ensures that the path property is set in the project.
|
||||||
*
|
*
|
||||||
* @param project the project to insert
|
* If it was not set, a new path is generated for it. Otherwise, the function
|
||||||
* @return
|
* acts as identity.
|
||||||
|
*
|
||||||
|
* @param project the project to find the path for
|
||||||
|
* @return the project, with the updated path
|
||||||
*/
|
*/
|
||||||
def create(project: Project): F[ProjectRepositoryFailure, Unit]
|
def findPathForNewProject(project: Project): F[ProjectRepositoryFailure, Path]
|
||||||
|
|
||||||
/** Saves the provided user project in the index.
|
/** Saves the provided user project in the index.
|
||||||
*
|
*
|
||||||
|
@ -3,12 +3,15 @@ package org.enso.projectmanager.model
|
|||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import org.enso.pkg.{DefaultEnsoVersion, EnsoVersion}
|
||||||
|
|
||||||
/** Project entity.
|
/** Project entity.
|
||||||
*
|
*
|
||||||
* @param id a project id
|
* @param id a project id
|
||||||
* @param name a project name
|
* @param name a project name
|
||||||
* @param kind a project kind
|
* @param kind a project kind
|
||||||
* @param created a project creation time
|
* @param created a project creation time
|
||||||
|
* @param engineVersion version of the engine associated with the project
|
||||||
* @param lastOpened a project last open time
|
* @param lastOpened a project last open time
|
||||||
* @param path a path to the project structure
|
* @param path a path to the project structure
|
||||||
*/
|
*/
|
||||||
@ -17,6 +20,7 @@ case class Project(
|
|||||||
name: String,
|
name: String,
|
||||||
kind: ProjectKind,
|
kind: ProjectKind,
|
||||||
created: OffsetDateTime,
|
created: OffsetDateTime,
|
||||||
|
engineVersion: EnsoVersion = DefaultEnsoVersion,
|
||||||
lastOpened: Option[OffsetDateTime] = None,
|
lastOpened: Option[OffsetDateTime] = None,
|
||||||
path: Option[String] = None
|
path: Option[String] = None
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,7 @@ import akka.actor.{Actor, ActorLogging, ActorRef, Props, Stash}
|
|||||||
import org.enso.jsonrpc.{JsonRpcServer, MessageHandler, Method, Request}
|
import org.enso.jsonrpc.{JsonRpcServer, MessageHandler, Method, Request}
|
||||||
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
||||||
import org.enso.projectmanager.control.core.CovariantFlatMap
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
import org.enso.projectmanager.control.effect.Exec
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Exec}
|
||||||
import org.enso.projectmanager.event.ClientEvent.{
|
import org.enso.projectmanager.event.ClientEvent.{
|
||||||
ClientConnected,
|
ClientConnected,
|
||||||
ClientDisconnected
|
ClientDisconnected
|
||||||
@ -30,7 +30,7 @@ import scala.concurrent.duration._
|
|||||||
* @param runtimeVersionManagementService version management service
|
* @param runtimeVersionManagementService version management service
|
||||||
* @param timeoutConfig a request timeout config
|
* @param timeoutConfig a request timeout config
|
||||||
*/
|
*/
|
||||||
class ClientController[F[+_, +_]: Exec: CovariantFlatMap](
|
class ClientController[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
projectService: ProjectServiceApi[F],
|
projectService: ProjectServiceApi[F],
|
||||||
globalConfigService: GlobalConfigServiceApi[F],
|
globalConfigService: GlobalConfigServiceApi[F],
|
||||||
@ -44,11 +44,19 @@ class ClientController[F[+_, +_]: Exec: CovariantFlatMap](
|
|||||||
private val requestHandlers: Map[Method, Props] =
|
private val requestHandlers: Map[Method, Props] =
|
||||||
Map(
|
Map(
|
||||||
ProjectCreate -> ProjectCreateHandler
|
ProjectCreate -> ProjectCreateHandler
|
||||||
.props[F](projectService, timeoutConfig.requestTimeout),
|
.props[F](
|
||||||
|
globalConfigService,
|
||||||
|
projectService,
|
||||||
|
timeoutConfig.requestTimeout
|
||||||
|
),
|
||||||
ProjectDelete -> ProjectDeleteHandler
|
ProjectDelete -> ProjectDeleteHandler
|
||||||
.props[F](projectService, timeoutConfig.requestTimeout),
|
.props[F](projectService, timeoutConfig.requestTimeout),
|
||||||
ProjectOpen -> ProjectOpenHandler
|
ProjectOpen -> ProjectOpenHandler
|
||||||
.props[F](clientId, projectService, timeoutConfig.bootTimeout),
|
.props[F](
|
||||||
|
clientId,
|
||||||
|
projectService,
|
||||||
|
timeoutConfig.bootTimeout
|
||||||
|
),
|
||||||
ProjectClose -> ProjectCloseHandler
|
ProjectClose -> ProjectCloseHandler
|
||||||
.props[F](
|
.props[F](
|
||||||
clientId,
|
clientId,
|
||||||
@ -118,7 +126,7 @@ object ClientController {
|
|||||||
* @param timeoutConfig a request timeout config
|
* @param timeoutConfig a request timeout config
|
||||||
* @return a configuration object
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props[F[+_, +_]: Exec: CovariantFlatMap](
|
def props[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
projectService: ProjectServiceApi[F],
|
projectService: ProjectServiceApi[F],
|
||||||
globalConfigService: GlobalConfigServiceApi[F],
|
globalConfigService: GlobalConfigServiceApi[F],
|
||||||
|
@ -6,7 +6,7 @@ import akka.actor.{ActorRef, ActorSystem}
|
|||||||
import org.enso.jsonrpc.ClientControllerFactory
|
import org.enso.jsonrpc.ClientControllerFactory
|
||||||
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
||||||
import org.enso.projectmanager.control.core.CovariantFlatMap
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
import org.enso.projectmanager.control.effect.Exec
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Exec}
|
||||||
import org.enso.projectmanager.service.ProjectServiceApi
|
import org.enso.projectmanager.service.ProjectServiceApi
|
||||||
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
||||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementServiceApi
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementServiceApi
|
||||||
@ -19,7 +19,9 @@ import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagemen
|
|||||||
* @param runtimeVersionManagementService version management service
|
* @param runtimeVersionManagementService version management service
|
||||||
* @param timeoutConfig a request timeout config
|
* @param timeoutConfig a request timeout config
|
||||||
*/
|
*/
|
||||||
class ManagerClientControllerFactory[F[+_, +_]: Exec: CovariantFlatMap](
|
class ManagerClientControllerFactory[
|
||||||
|
F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel
|
||||||
|
](
|
||||||
system: ActorSystem,
|
system: ActorSystem,
|
||||||
projectService: ProjectServiceApi[F],
|
projectService: ProjectServiceApi[F],
|
||||||
globalConfigService: GlobalConfigServiceApi[F],
|
globalConfigService: GlobalConfigServiceApi[F],
|
||||||
|
@ -321,4 +321,8 @@ object ProjectManagementApi {
|
|||||||
case class GlobalConfigurationAccessError(msg: String)
|
case class GlobalConfigurationAccessError(msg: String)
|
||||||
extends Error(4011, msg)
|
extends Error(4011, msg)
|
||||||
|
|
||||||
|
case class ProjectCreateError(msg: String) extends Error(4012, msg)
|
||||||
|
|
||||||
|
case class LoggingServiceUnavailable(msg: String) extends Error(4013, msg)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,11 @@ class EngineInstallHandler[F[+_, +_]: Exec: CovariantFlatMap](
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def handleRequest = { params =>
|
override def handleRequest = { params =>
|
||||||
val progressTracker = sender()
|
|
||||||
for {
|
for {
|
||||||
_ <- service.installEngine(
|
_ <- service.installEngine(
|
||||||
progressTracker,
|
progressTracker = self,
|
||||||
params.version,
|
version = params.version,
|
||||||
params.forceInstallBroken.getOrElse(false)
|
forceInstallBroken = params.forceInstallBroken.getOrElse(false)
|
||||||
)
|
)
|
||||||
} yield Unused
|
} yield Unused
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,11 @@ class EngineUninstallHandler[F[+_, +_]: Exec: CovariantFlatMap](
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def handleRequest = { params =>
|
override def handleRequest = { params =>
|
||||||
val progressTracker = sender()
|
|
||||||
for {
|
for {
|
||||||
_ <- service.uninstallEngine(progressTracker, params.version)
|
_ <- service.uninstallEngine(
|
||||||
|
progressTracker = self,
|
||||||
|
version = params.version
|
||||||
|
)
|
||||||
} yield Unused
|
} yield Unused
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,109 +1,86 @@
|
|||||||
package org.enso.projectmanager.requesthandler
|
package org.enso.projectmanager.requesthandler
|
||||||
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import akka.pattern.pipe
|
|
||||||
import org.enso.jsonrpc.Errors.{NotImplementedError, ServiceError}
|
|
||||||
import org.enso.jsonrpc._
|
|
||||||
import org.enso.pkg.DefaultEnsoVersion
|
import org.enso.pkg.DefaultEnsoVersion
|
||||||
import org.enso.projectmanager.control.effect.Exec
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
|
import org.enso.projectmanager.control.core.syntax._
|
||||||
|
import org.enso.projectmanager.control.effect.syntax._
|
||||||
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Exec}
|
||||||
import org.enso.projectmanager.data.MissingComponentAction
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
import org.enso.projectmanager.protocol.ProjectManagementApi.ProjectCreate
|
import org.enso.projectmanager.protocol.ProjectManagementApi.ProjectCreate
|
||||||
import org.enso.projectmanager.requesthandler.ProjectServiceFailureMapper.mapFailure
|
import org.enso.projectmanager.requesthandler.ProjectServiceFailureMapper.failureMapper
|
||||||
|
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
||||||
import org.enso.projectmanager.service.{
|
import org.enso.projectmanager.service.{
|
||||||
ProjectServiceApi,
|
ProjectServiceApi,
|
||||||
ProjectServiceFailure
|
ProjectServiceFailure
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.util.UnhandledLogging
|
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
/** A request handler for `project/create` commands.
|
/** A request handler for `project/create` commands.
|
||||||
*
|
*
|
||||||
* @param service a project service
|
* @param configurationService the configuration service
|
||||||
|
* @param projectService a project service
|
||||||
* @param requestTimeout a request timeout
|
* @param requestTimeout a request timeout
|
||||||
*/
|
*/
|
||||||
class ProjectCreateHandler[F[+_, +_]: Exec](
|
class ProjectCreateHandler[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||||
service: ProjectServiceApi[F],
|
configurationService: GlobalConfigServiceApi[F],
|
||||||
|
projectService: ProjectServiceApi[F],
|
||||||
requestTimeout: FiniteDuration
|
requestTimeout: FiniteDuration
|
||||||
) extends Actor
|
) extends RequestHandler[
|
||||||
with ActorLogging
|
F,
|
||||||
with UnhandledLogging {
|
ProjectServiceFailure,
|
||||||
override def receive: Receive = requestStage
|
ProjectCreate.type,
|
||||||
|
ProjectCreate.Params,
|
||||||
|
ProjectCreate.Result
|
||||||
|
](
|
||||||
|
ProjectCreate,
|
||||||
|
Some(requestTimeout)
|
||||||
|
) {
|
||||||
|
|
||||||
import context.dispatcher
|
override def handleRequest = { params =>
|
||||||
|
val version = params.version.getOrElse(DefaultEnsoVersion)
|
||||||
|
val missingComponentAction =
|
||||||
|
params.missingComponentAction.getOrElse(MissingComponentAction.Fail)
|
||||||
|
|
||||||
private def requestStage: Receive = {
|
for {
|
||||||
case Request(ProjectCreate, id, params: ProjectCreate.Params) =>
|
actualVersion <- configurationService
|
||||||
if (params.version.isDefined) {
|
.resolveEnsoVersion(version)
|
||||||
// TODO [RW] just to indicate that choosing specific version is not yet
|
.mapError { error =>
|
||||||
// implemented, should be removed once that functionality is added
|
ProjectServiceFailure.ComponentRepositoryAccessFailure(
|
||||||
sender() ! ResponseError(Some(id), NotImplementedError)
|
s"Could not determine the default version: $error"
|
||||||
context.stop(self)
|
|
||||||
} else {
|
|
||||||
val version = params.version.getOrElse(DefaultEnsoVersion)
|
|
||||||
val missingComponentAction =
|
|
||||||
params.missingComponentAction.getOrElse(MissingComponentAction.Fail)
|
|
||||||
Exec[F]
|
|
||||||
.exec(
|
|
||||||
service
|
|
||||||
.createUserProject(params.name, version, missingComponentAction)
|
|
||||||
)
|
)
|
||||||
.pipeTo(self)
|
}
|
||||||
val cancellable =
|
projectId <- projectService.createUserProject(
|
||||||
context.system.scheduler
|
progressTracker = self,
|
||||||
.scheduleOnce(requestTimeout, self, RequestTimeout)
|
name = params.name,
|
||||||
context.become(responseStage(id, sender(), cancellable))
|
engineVersion = actualVersion,
|
||||||
}
|
missingComponentAction = missingComponentAction
|
||||||
}
|
|
||||||
|
|
||||||
private def responseStage(
|
|
||||||
id: Id,
|
|
||||||
replyTo: ActorRef,
|
|
||||||
cancellable: Cancellable
|
|
||||||
): Receive = {
|
|
||||||
case Status.Failure(ex) =>
|
|
||||||
log.error(ex, s"Failure during $ProjectCreate operation:")
|
|
||||||
replyTo ! ResponseError(Some(id), ServiceError)
|
|
||||||
cancellable.cancel()
|
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case RequestTimeout =>
|
|
||||||
log.error(s"Request $ProjectCreate with $id timed out")
|
|
||||||
replyTo ! ResponseError(Some(id), ServiceError)
|
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case Left(failure: ProjectServiceFailure) =>
|
|
||||||
log.error(s"Request $id failed due to $failure")
|
|
||||||
replyTo ! ResponseError(Some(id), mapFailure(failure))
|
|
||||||
cancellable.cancel()
|
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case Right(projectId: UUID) =>
|
|
||||||
replyTo ! ResponseResult(
|
|
||||||
ProjectCreate,
|
|
||||||
id,
|
|
||||||
ProjectCreate.Result(projectId)
|
|
||||||
)
|
)
|
||||||
cancellable.cancel()
|
} yield ProjectCreate.Result(projectId)
|
||||||
context.stop(self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object ProjectCreateHandler {
|
object ProjectCreateHandler {
|
||||||
|
|
||||||
/** Creates a configuration object used to create a [[ProjectCreateHandler]].
|
/** Creates a configuration object used to create a [[ProjectCreateHandler]].
|
||||||
*
|
*
|
||||||
* @param service a project service
|
* @param configurationService
|
||||||
|
* @param projectService a project service
|
||||||
* @param requestTimeout a request timeout
|
* @param requestTimeout a request timeout
|
||||||
* @return a configuration object
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props[F[+_, +_]: Exec](
|
def props[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||||
service: ProjectServiceApi[F],
|
configurationService: GlobalConfigServiceApi[F],
|
||||||
|
projectService: ProjectServiceApi[F],
|
||||||
requestTimeout: FiniteDuration
|
requestTimeout: FiniteDuration
|
||||||
): Props =
|
): Props =
|
||||||
Props(new ProjectCreateHandler(service, requestTimeout))
|
Props(
|
||||||
|
new ProjectCreateHandler(
|
||||||
|
configurationService,
|
||||||
|
projectService,
|
||||||
|
requestTimeout
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,88 +2,59 @@ package org.enso.projectmanager.requesthandler
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props, Status}
|
import akka.actor.Props
|
||||||
import akka.pattern.pipe
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
import org.enso.jsonrpc.Errors.ServiceError
|
import org.enso.projectmanager.control.core.syntax._
|
||||||
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
|
|
||||||
import org.enso.projectmanager.control.effect.Exec
|
import org.enso.projectmanager.control.effect.Exec
|
||||||
import org.enso.projectmanager.data.{
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
LanguageServerSockets,
|
|
||||||
MissingComponentAction
|
|
||||||
}
|
|
||||||
import org.enso.projectmanager.protocol.ProjectManagementApi.ProjectOpen
|
import org.enso.projectmanager.protocol.ProjectManagementApi.ProjectOpen
|
||||||
import org.enso.projectmanager.requesthandler.ProjectServiceFailureMapper.mapFailure
|
import org.enso.projectmanager.requesthandler.ProjectServiceFailureMapper.failureMapper
|
||||||
import org.enso.projectmanager.service.{
|
import org.enso.projectmanager.service.{
|
||||||
ProjectServiceApi,
|
ProjectServiceApi,
|
||||||
ProjectServiceFailure
|
ProjectServiceFailure
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.util.UnhandledLogging
|
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
/** A request handler for `project/open` commands.
|
/** A request handler for `project/open` commands.
|
||||||
*
|
*
|
||||||
* @param clientId the requester id
|
* @param clientId the requester id
|
||||||
* @param service a project service
|
* @param projectService a project service
|
||||||
* @param requestTimeout a request timeout
|
* @param requestTimeout a request timeout
|
||||||
*/
|
*/
|
||||||
class ProjectOpenHandler[F[+_, +_]: Exec](
|
class ProjectOpenHandler[F[+_, +_]: Exec: CovariantFlatMap](
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
service: ProjectServiceApi[F],
|
projectService: ProjectServiceApi[F],
|
||||||
requestTimeout: FiniteDuration
|
requestTimeout: FiniteDuration
|
||||||
) extends Actor
|
) extends RequestHandler[
|
||||||
with ActorLogging
|
F,
|
||||||
with UnhandledLogging {
|
ProjectServiceFailure,
|
||||||
override def receive: Receive = requestStage
|
ProjectOpen.type,
|
||||||
|
ProjectOpen.Params,
|
||||||
|
ProjectOpen.Result
|
||||||
|
](
|
||||||
|
ProjectOpen,
|
||||||
|
// TODO [RW] maybe we can get rid of this timeout since boot timeout is
|
||||||
|
// handled by the LanguageServerProcess; still the ? message of
|
||||||
|
// LanguageServerGateway will result in timeouts
|
||||||
|
Some(requestTimeout)
|
||||||
|
) {
|
||||||
|
|
||||||
import context.dispatcher
|
override def handleRequest = { params =>
|
||||||
|
val missingComponentAction =
|
||||||
|
params.missingComponentAction.getOrElse(MissingComponentAction.Fail)
|
||||||
|
|
||||||
private def requestStage: Receive = {
|
for {
|
||||||
case Request(ProjectOpen, id, params: ProjectOpen.Params) =>
|
sockets <- projectService.openProject(
|
||||||
val missingComponentAction =
|
progressTracker = self,
|
||||||
params.missingComponentAction.getOrElse(MissingComponentAction.Fail)
|
clientId = clientId,
|
||||||
Exec[F]
|
projectId = params.projectId,
|
||||||
.exec(
|
missingComponentAction = missingComponentAction
|
||||||
service
|
|
||||||
.openProject(clientId, params.projectId, missingComponentAction)
|
|
||||||
)
|
|
||||||
.pipeTo(self)
|
|
||||||
val cancellable =
|
|
||||||
context.system.scheduler
|
|
||||||
.scheduleOnce(requestTimeout, self, RequestTimeout)
|
|
||||||
context.become(responseStage(id, sender(), cancellable))
|
|
||||||
}
|
|
||||||
|
|
||||||
private def responseStage(
|
|
||||||
id: Id,
|
|
||||||
replyTo: ActorRef,
|
|
||||||
cancellable: Cancellable
|
|
||||||
): Receive = {
|
|
||||||
case Status.Failure(ex) =>
|
|
||||||
log.error(ex, s"Failure during $ProjectOpen operation:")
|
|
||||||
replyTo ! ResponseError(Some(id), ServiceError)
|
|
||||||
cancellable.cancel()
|
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case RequestTimeout =>
|
|
||||||
log.error(s"Request $ProjectOpen with $id timed out")
|
|
||||||
replyTo ! ResponseError(Some(id), ServiceError)
|
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case Left(failure: ProjectServiceFailure) =>
|
|
||||||
log.error(s"Request $id failed due to $failure")
|
|
||||||
replyTo ! ResponseError(Some(id), mapFailure(failure))
|
|
||||||
cancellable.cancel()
|
|
||||||
context.stop(self)
|
|
||||||
|
|
||||||
case Right(sockets: LanguageServerSockets) =>
|
|
||||||
replyTo ! ResponseResult(
|
|
||||||
ProjectOpen,
|
|
||||||
id,
|
|
||||||
ProjectOpen.Result(sockets.jsonSocket, sockets.binarySocket)
|
|
||||||
)
|
)
|
||||||
cancellable.cancel()
|
} yield ProjectOpen.Result(
|
||||||
context.stop(self)
|
languageServerJsonAddress = sockets.jsonSocket,
|
||||||
|
languageServerBinaryAddress = sockets.binarySocket
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -93,15 +64,21 @@ object ProjectOpenHandler {
|
|||||||
/** Creates a configuration object used to create a [[ProjectOpenHandler]].
|
/** Creates a configuration object used to create a [[ProjectOpenHandler]].
|
||||||
*
|
*
|
||||||
* @param clientId the requester id
|
* @param clientId the requester id
|
||||||
* @param service a project service
|
* @param projectService a project service
|
||||||
* @param requestTimeout a request timeout
|
* @param requestTimeout a request timeout
|
||||||
* @return a configuration object
|
* @return a configuration object
|
||||||
*/
|
*/
|
||||||
def props[F[+_, +_]: Exec](
|
def props[F[+_, +_]: Exec: CovariantFlatMap](
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
service: ProjectServiceApi[F],
|
projectService: ProjectServiceApi[F],
|
||||||
requestTimeout: FiniteDuration
|
requestTimeout: FiniteDuration
|
||||||
): Props =
|
): Props =
|
||||||
Props(new ProjectOpenHandler(clientId, service, requestTimeout))
|
Props(
|
||||||
|
new ProjectOpenHandler(
|
||||||
|
clientId,
|
||||||
|
projectService,
|
||||||
|
requestTimeout
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ object ProjectServiceFailureMapper {
|
|||||||
case DataStoreFailure(msg) => ProjectDataStoreError(msg)
|
case DataStoreFailure(msg) => ProjectDataStoreError(msg)
|
||||||
case ProjectExists => ProjectExistsError
|
case ProjectExists => ProjectExistsError
|
||||||
case ProjectNotFound => ProjectNotFoundError
|
case ProjectNotFound => ProjectNotFoundError
|
||||||
|
case ProjectCreateFailed(msg) => ProjectCreateError(msg)
|
||||||
case ProjectOpenFailed(msg) => ProjectOpenError(msg)
|
case ProjectOpenFailed(msg) => ProjectOpenError(msg)
|
||||||
case ProjectCloseFailed(msg) => ProjectCloseError(msg)
|
case ProjectCloseFailed(msg) => ProjectCloseError(msg)
|
||||||
case ProjectNotOpen => ProjectNotOpenError
|
case ProjectNotOpen => ProjectNotOpenError
|
||||||
|
@ -61,7 +61,7 @@ abstract class RequestHandler[
|
|||||||
.exec(result)
|
.exec(result)
|
||||||
.map(_.map(ResponseResult(method, request.id, _)))
|
.map(_.map(ResponseResult(method, request.id, _)))
|
||||||
.pipeTo(self)
|
.pipeTo(self)
|
||||||
val cancellable = {
|
val timeoutCancellable = {
|
||||||
requestTimeout.map { timeout =>
|
requestTimeout.map { timeout =>
|
||||||
context.system.scheduler.scheduleOnce(
|
context.system.scheduler.scheduleOnce(
|
||||||
timeout,
|
timeout,
|
||||||
@ -70,7 +70,7 @@ abstract class RequestHandler[
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.become(responseStage(request.id, sender(), cancellable))
|
context.become(responseStage(request.id, sender(), timeoutCancellable))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defines the actual logic for handling the request.
|
/** Defines the actual logic for handling the request.
|
||||||
@ -86,12 +86,12 @@ abstract class RequestHandler[
|
|||||||
private def responseStage(
|
private def responseStage(
|
||||||
id: Id,
|
id: Id,
|
||||||
replyTo: ActorRef,
|
replyTo: ActorRef,
|
||||||
cancellable: Option[Cancellable]
|
timeoutCancellable: Option[Cancellable]
|
||||||
): Receive = {
|
): Receive = {
|
||||||
case Status.Failure(ex) =>
|
case Status.Failure(ex) =>
|
||||||
log.error(ex, s"Failure during $method operation:")
|
log.error(ex, s"Failure during $method operation:")
|
||||||
replyTo ! ResponseError(Some(id), ServiceError)
|
replyTo ! ResponseError(Some(id), ServiceError)
|
||||||
cancellable.foreach(_.cancel())
|
timeoutCancellable.foreach(_.cancel())
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
|
||||||
case RequestTimeout =>
|
case RequestTimeout =>
|
||||||
@ -103,15 +103,34 @@ abstract class RequestHandler[
|
|||||||
log.error(s"Request $id failed due to $failure")
|
log.error(s"Request $id failed due to $failure")
|
||||||
val error = implicitly[FailureMapper[FailureType]].mapFailure(failure)
|
val error = implicitly[FailureMapper[FailureType]].mapFailure(failure)
|
||||||
replyTo ! ResponseError(Some(id), error)
|
replyTo ! ResponseError(Some(id), error)
|
||||||
cancellable.foreach(_.cancel())
|
timeoutCancellable.foreach(_.cancel())
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
|
||||||
case Right(response) =>
|
case Right(response) =>
|
||||||
replyTo ! response
|
replyTo ! response
|
||||||
cancellable.foreach(_.cancel())
|
timeoutCancellable.foreach(_.cancel())
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
|
||||||
case notification: ProgressNotification =>
|
case notification: ProgressNotification =>
|
||||||
|
notification match {
|
||||||
|
case ProgressNotification.TaskStarted(_, _, _) =>
|
||||||
|
abandonTimeout(id, replyTo, timeoutCancellable)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
replyTo ! translateProgressNotification(method.name, notification)
|
replyTo ! translateProgressNotification(method.name, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cancels the timeout operation.
|
||||||
|
*
|
||||||
|
* Should be called when a long-running task is detected that we do not want
|
||||||
|
* to interrupt.
|
||||||
|
*/
|
||||||
|
private def abandonTimeout(
|
||||||
|
id: Id,
|
||||||
|
replyTo: ActorRef,
|
||||||
|
timeoutCancellable: Option[Cancellable]
|
||||||
|
): Unit = {
|
||||||
|
timeoutCancellable.foreach(_.cancel())
|
||||||
|
context.become(responseStage(id, replyTo, None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package org.enso.projectmanager.service
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
|
import org.enso.projectmanager.control.core.syntax._
|
||||||
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
||||||
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
|
import org.enso.projectmanager.service.ProjectServiceFailure.ProjectCreateFailed
|
||||||
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._
|
||||||
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
|
||||||
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
|
import org.enso.runtimeversionmanager.runner.Runner
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
/** A service for creating new project structures using the runner of the
|
||||||
|
* specific engine version selected for the project.
|
||||||
|
*/
|
||||||
|
class ProjectCreationService[
|
||||||
|
F[+_, +_]: Sync: ErrorChannel: CovariantFlatMap
|
||||||
|
](
|
||||||
|
distributionConfiguration: DistributionConfiguration
|
||||||
|
) extends ProjectCreationServiceApi[F] {
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def createProject(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
path: Path,
|
||||||
|
name: String,
|
||||||
|
engineVersion: SemVer,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): F[ProjectServiceFailure, Unit] = Sync[F]
|
||||||
|
.blockingOp {
|
||||||
|
val versionManager = RuntimeVersionManagerFactory(
|
||||||
|
distributionConfiguration
|
||||||
|
).makeRuntimeVersionManager(progressTracker, missingComponentAction)
|
||||||
|
val runner =
|
||||||
|
new Runner(
|
||||||
|
versionManager,
|
||||||
|
distributionConfiguration.environment,
|
||||||
|
Future.successful(None)
|
||||||
|
)
|
||||||
|
|
||||||
|
val settings =
|
||||||
|
runner.newProject(path, name, engineVersion, None, None, Seq()).get
|
||||||
|
val jvmSettings = distributionConfiguration.defaultJVMSettings
|
||||||
|
runner.withCommand(settings, jvmSettings) { command =>
|
||||||
|
command.run().get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mapRuntimeManagerErrors { other: Throwable =>
|
||||||
|
ProjectCreateFailed(other.getMessage)
|
||||||
|
}
|
||||||
|
.flatMap { exitCode =>
|
||||||
|
if (exitCode == 0)
|
||||||
|
CovariantFlatMap[F].pure(())
|
||||||
|
else
|
||||||
|
ErrorChannel[F].fail(
|
||||||
|
ProjectCreateFailed(
|
||||||
|
s"The runner used to create the project returned exit code " +
|
||||||
|
s"$exitCode."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package org.enso.projectmanager.service
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
|
|
||||||
|
/** An abstraction for creating new project structures under the given location.
|
||||||
|
*/
|
||||||
|
trait ProjectCreationServiceApi[F[+_, +_]] {
|
||||||
|
|
||||||
|
/** Creates a project with the provided configuration.
|
||||||
|
*
|
||||||
|
* @param progressTracker an actor that will be sent notifcation regarding
|
||||||
|
* progress of installation of any missing components
|
||||||
|
* or waiting on locks
|
||||||
|
* @param path path at which to create the project
|
||||||
|
* @param name name of the project
|
||||||
|
* @param engineVersion version of the engine this project is meant for
|
||||||
|
* @param missingComponentAction specifies how to handle missing components
|
||||||
|
*/
|
||||||
|
def createProject(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
path: Path,
|
||||||
|
name: String,
|
||||||
|
engineVersion: SemVer,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): F[ProjectServiceFailure, Unit]
|
||||||
|
}
|
@ -2,19 +2,21 @@ package org.enso.projectmanager.service
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
import cats.MonadError
|
import cats.MonadError
|
||||||
import org.enso.pkg.{EnsoVersion, PackageManager}
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.pkg.PackageManager
|
||||||
import org.enso.projectmanager.control.core.CovariantFlatMap
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
import org.enso.projectmanager.control.core.syntax._
|
import org.enso.projectmanager.control.core.syntax._
|
||||||
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
|
||||||
import org.enso.projectmanager.control.effect.syntax._
|
import org.enso.projectmanager.control.effect.syntax._
|
||||||
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
||||||
import org.enso.projectmanager.data.{
|
import org.enso.projectmanager.data.{
|
||||||
LanguageServerSockets,
|
LanguageServerSockets,
|
||||||
MissingComponentAction,
|
MissingComponentAction,
|
||||||
ProjectMetadata
|
ProjectMetadata
|
||||||
}
|
}
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol._
|
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerGateway
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerGateway
|
||||||
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol._
|
||||||
import org.enso.projectmanager.infrastructure.log.Logging
|
import org.enso.projectmanager.infrastructure.log.Logging
|
||||||
import org.enso.projectmanager.infrastructure.random.Generator
|
import org.enso.projectmanager.infrastructure.random.Generator
|
||||||
import org.enso.projectmanager.infrastructure.repository.ProjectRepositoryFailure.{
|
import org.enso.projectmanager.infrastructure.repository.ProjectRepositoryFailure.{
|
||||||
@ -35,11 +37,18 @@ import org.enso.projectmanager.service.ValidationFailure.{
|
|||||||
EmptyName,
|
EmptyName,
|
||||||
NameContainsForbiddenCharacter
|
NameContainsForbiddenCharacter
|
||||||
}
|
}
|
||||||
|
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
||||||
|
import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure
|
||||||
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._
|
||||||
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
|
||||||
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
|
|
||||||
/** Implementation of business logic for project management.
|
/** Implementation of business logic for project management.
|
||||||
*
|
*
|
||||||
* @param validator a project validator
|
* @param validator a project validator
|
||||||
* @param repo a project repository
|
* @param repo a project repository
|
||||||
|
* @param projectCreationService a service for creating projects
|
||||||
|
* @param configurationService a service for managing configuration
|
||||||
* @param log a logging facility
|
* @param log a logging facility
|
||||||
* @param clock a clock
|
* @param clock a clock
|
||||||
* @param gen a random generator
|
* @param gen a random generator
|
||||||
@ -47,10 +56,13 @@ import org.enso.projectmanager.service.ValidationFailure.{
|
|||||||
class ProjectService[F[+_, +_]: ErrorChannel: CovariantFlatMap: Sync](
|
class ProjectService[F[+_, +_]: ErrorChannel: CovariantFlatMap: Sync](
|
||||||
validator: ProjectValidator[F],
|
validator: ProjectValidator[F],
|
||||||
repo: ProjectRepository[F],
|
repo: ProjectRepository[F],
|
||||||
|
projectCreationService: ProjectCreationServiceApi[F],
|
||||||
|
configurationService: GlobalConfigServiceApi[F],
|
||||||
log: Logging[F],
|
log: Logging[F],
|
||||||
clock: Clock[F],
|
clock: Clock[F],
|
||||||
gen: Generator[F],
|
gen: Generator[F],
|
||||||
languageServerGateway: LanguageServerGateway[F]
|
languageServerGateway: LanguageServerGateway[F],
|
||||||
|
distributionConfiguration: DistributionConfiguration
|
||||||
)(implicit E: MonadError[F[ProjectServiceFailure, *], ProjectServiceFailure])
|
)(implicit E: MonadError[F[ProjectServiceFailure, *], ProjectServiceFailure])
|
||||||
extends ProjectServiceApi[F] {
|
extends ProjectServiceApi[F] {
|
||||||
|
|
||||||
@ -58,25 +70,30 @@ class ProjectService[F[+_, +_]: ErrorChannel: CovariantFlatMap: Sync](
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def createUserProject(
|
override def createUserProject(
|
||||||
|
progressTracker: ActorRef,
|
||||||
name: String,
|
name: String,
|
||||||
version: EnsoVersion,
|
engineVersion: SemVer,
|
||||||
missingComponentAction: MissingComponentAction
|
missingComponentAction: MissingComponentAction
|
||||||
): F[ProjectServiceFailure, UUID] = {
|
): F[ProjectServiceFailure, UUID] = for {
|
||||||
// TODO [RW] new component handling
|
projectId <- gen.randomUUID()
|
||||||
val _ = (version, missingComponentAction)
|
_ <- log.debug(s"Creating project $name $projectId.")
|
||||||
// format: off
|
_ <- validateName(name)
|
||||||
for {
|
_ <- checkIfNameExists(name)
|
||||||
projectId <- gen.randomUUID()
|
creationTime <- clock.nowInUtc()
|
||||||
_ <- log.debug(s"Creating project $name $projectId.")
|
project = Project(projectId, name, UserProject, creationTime)
|
||||||
_ <- validateName(name)
|
path <- repo.findPathForNewProject(project).mapError(toServiceFailure)
|
||||||
_ <- checkIfNameExists(name)
|
_ <- projectCreationService.createProject(
|
||||||
creationTime <- clock.nowInUtc()
|
progressTracker,
|
||||||
project = Project(projectId, name, UserProject, creationTime)
|
path,
|
||||||
_ <- repo.create(project).mapError(toServiceFailure)
|
name,
|
||||||
_ <- log.info(s"Project $project created.")
|
engineVersion,
|
||||||
} yield projectId
|
missingComponentAction
|
||||||
// format: on
|
)
|
||||||
}
|
_ <- repo
|
||||||
|
.update(project.copy(path = Some(path.toString)))
|
||||||
|
.mapError(toServiceFailure)
|
||||||
|
_ <- log.info(s"Project $project created.")
|
||||||
|
} yield projectId
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def deleteUserProject(
|
override def deleteUserProject(
|
||||||
@ -176,12 +193,11 @@ class ProjectService[F[+_, +_]: ErrorChannel: CovariantFlatMap: Sync](
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def openProject(
|
override def openProject(
|
||||||
|
progressTracker: ActorRef,
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
projectId: UUID,
|
projectId: UUID,
|
||||||
missingComponentAction: MissingComponentAction
|
missingComponentAction: MissingComponentAction
|
||||||
): F[ProjectServiceFailure, LanguageServerSockets] = {
|
): F[ProjectServiceFailure, LanguageServerSockets] = {
|
||||||
// TODO [RW] new component handling
|
|
||||||
val _ = missingComponentAction
|
|
||||||
// format: off
|
// format: off
|
||||||
for {
|
for {
|
||||||
_ <- log.debug(s"Opening project $projectId")
|
_ <- log.debug(s"Opening project $projectId")
|
||||||
@ -189,17 +205,46 @@ class ProjectService[F[+_, +_]: ErrorChannel: CovariantFlatMap: Sync](
|
|||||||
openTime <- clock.nowInUtc()
|
openTime <- clock.nowInUtc()
|
||||||
updated = project.copy(lastOpened = Some(openTime))
|
updated = project.copy(lastOpened = Some(openTime))
|
||||||
_ <- repo.update(updated).mapError(toServiceFailure)
|
_ <- repo.update(updated).mapError(toServiceFailure)
|
||||||
sockets <- startServer(clientId, updated)
|
sockets <- startServer(progressTracker, clientId, updated, missingComponentAction)
|
||||||
} yield sockets
|
} yield sockets
|
||||||
// format: on
|
// format: on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def preinstallEngine(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
version: SemVer,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): F[ProjectServiceFailure, Unit] =
|
||||||
|
Sync[F]
|
||||||
|
.blockingOp {
|
||||||
|
RuntimeVersionManagerFactory(distributionConfiguration)
|
||||||
|
.makeRuntimeVersionManager(progressTracker, missingComponentAction)
|
||||||
|
.findOrInstallEngine(version)
|
||||||
|
()
|
||||||
|
}
|
||||||
|
.mapRuntimeManagerErrors(th =>
|
||||||
|
ProjectOpenFailed(
|
||||||
|
s"Cannot install the required engine ${th.getMessage}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
private def startServer(
|
private def startServer(
|
||||||
|
progressTracker: ActorRef,
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
project: Project
|
project: Project,
|
||||||
): F[ProjectServiceFailure, LanguageServerSockets] =
|
missingComponentAction: MissingComponentAction
|
||||||
languageServerGateway
|
): F[ProjectServiceFailure, LanguageServerSockets] = for {
|
||||||
.start(clientId, project)
|
version <- configurationService
|
||||||
|
.resolveEnsoVersion(project.engineVersion)
|
||||||
|
.mapError { case ConfigurationFileAccessFailure(message) =>
|
||||||
|
ProjectOpenFailed(
|
||||||
|
s"Could not deduce the default version to use for the project: " +
|
||||||
|
s"$message"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ <- preinstallEngine(progressTracker, version, missingComponentAction)
|
||||||
|
sockets <- languageServerGateway
|
||||||
|
.start(progressTracker, clientId, project, version)
|
||||||
.mapError {
|
.mapError {
|
||||||
case PreviousInstanceNotShutDown =>
|
case PreviousInstanceNotShutDown =>
|
||||||
ProjectOpenFailed(
|
ProjectOpenFailed(
|
||||||
@ -215,6 +260,7 @@ class ProjectService[F[+_, +_]: ErrorChannel: CovariantFlatMap: Sync](
|
|||||||
s"Language server boot failed: ${th.getMessage}"
|
s"Language server boot failed: ${th.getMessage}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} yield sockets
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def closeProject(
|
override def closeProject(
|
||||||
|
@ -2,7 +2,8 @@ package org.enso.projectmanager.service
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import org.enso.pkg.EnsoVersion
|
import akka.actor.ActorRef
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.projectmanager.data.{
|
import org.enso.projectmanager.data.{
|
||||||
LanguageServerSockets,
|
LanguageServerSockets,
|
||||||
MissingComponentAction,
|
MissingComponentAction,
|
||||||
@ -17,14 +18,16 @@ trait ProjectServiceApi[F[+_, +_]] {
|
|||||||
|
|
||||||
/** Creates a user project.
|
/** Creates a user project.
|
||||||
*
|
*
|
||||||
|
* @param progressTracker the actor to send progress updates to
|
||||||
* @param name the name of th project
|
* @param name the name of th project
|
||||||
* @param version Enso version to use for the new project
|
* @param engineVersion Enso version to use for the new project
|
||||||
* @param missingComponentAction specifies how to handle missing components
|
* @param missingComponentAction specifies how to handle missing components
|
||||||
* @return projectId
|
* @return projectId
|
||||||
*/
|
*/
|
||||||
def createUserProject(
|
def createUserProject(
|
||||||
|
progressTracker: ActorRef,
|
||||||
name: String,
|
name: String,
|
||||||
version: EnsoVersion,
|
engineVersion: SemVer,
|
||||||
missingComponentAction: MissingComponentAction
|
missingComponentAction: MissingComponentAction
|
||||||
): F[ProjectServiceFailure, UUID]
|
): F[ProjectServiceFailure, UUID]
|
||||||
|
|
||||||
@ -48,11 +51,14 @@ trait ProjectServiceApi[F[+_, +_]] {
|
|||||||
|
|
||||||
/** Opens a project. It starts up a Language Server if needed.
|
/** Opens a project. It starts up a Language Server if needed.
|
||||||
*
|
*
|
||||||
|
* @param progressTracker the actor to send progress updates to
|
||||||
* @param clientId the requester id
|
* @param clientId the requester id
|
||||||
* @param projectId the project id
|
* @param projectId the project id
|
||||||
|
* @param missingComponentAction specifies how to handle missing components
|
||||||
* @return either failure or a socket of the Language Server
|
* @return either failure or a socket of the Language Server
|
||||||
*/
|
*/
|
||||||
def openProject(
|
def openProject(
|
||||||
|
progressTracker: ActorRef,
|
||||||
clientId: UUID,
|
clientId: UUID,
|
||||||
projectId: UUID,
|
projectId: UUID,
|
||||||
missingComponentAction: MissingComponentAction
|
missingComponentAction: MissingComponentAction
|
||||||
|
@ -28,6 +28,12 @@ object ProjectServiceFailure {
|
|||||||
*/
|
*/
|
||||||
case object ProjectNotFound extends ProjectServiceFailure
|
case object ProjectNotFound extends ProjectServiceFailure
|
||||||
|
|
||||||
|
/** Signals that a failure occured when creating the project.
|
||||||
|
*
|
||||||
|
* @param message a failure message
|
||||||
|
*/
|
||||||
|
case class ProjectCreateFailed(message: String) extends ProjectServiceFailure
|
||||||
|
|
||||||
/** Signals that a failure occurred during project startup.
|
/** Signals that a failure occurred during project startup.
|
||||||
*
|
*
|
||||||
* @param message a failure message
|
* @param message a failure message
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package org.enso.projectmanager.service.config
|
package org.enso.projectmanager.service.config
|
||||||
|
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.pkg.{DefaultEnsoVersion, EnsoVersion, SemVerEnsoVersion}
|
||||||
|
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||||
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
|
||||||
import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure
|
import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure
|
||||||
import org.enso.projectmanager.service.versionmanagement.NoOpInterface
|
import org.enso.projectmanager.service.versionmanagement.NoOpInterface
|
||||||
@ -11,7 +14,7 @@ import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
|
|||||||
*
|
*
|
||||||
* @param distributionConfiguration a distribution configuration
|
* @param distributionConfiguration a distribution configuration
|
||||||
*/
|
*/
|
||||||
class GlobalConfigService[F[+_, +_]: Sync: ErrorChannel](
|
class GlobalConfigService[F[+_, +_]: Sync: ErrorChannel: CovariantFlatMap](
|
||||||
distributionConfiguration: DistributionConfiguration
|
distributionConfiguration: DistributionConfiguration
|
||||||
) extends GlobalConfigServiceApi[F] {
|
) extends GlobalConfigServiceApi[F] {
|
||||||
|
|
||||||
@ -20,26 +23,46 @@ class GlobalConfigService[F[+_, +_]: Sync: ErrorChannel](
|
|||||||
distributionConfiguration.distributionManager
|
distributionConfiguration.distributionManager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
override def getKey(
|
override def getKey(
|
||||||
key: String
|
key: String
|
||||||
): F[GlobalConfigServiceFailure, Option[String]] =
|
): F[GlobalConfigServiceFailure, Option[String]] =
|
||||||
Sync[F].blockingIO {
|
Sync[F].blockingOp {
|
||||||
val valueOption = configurationManager.getConfig.original.apply(key)
|
val valueOption = configurationManager.getConfig.original.apply(key)
|
||||||
valueOption.map(json => json.asString.getOrElse(json.toString()))
|
valueOption.map(json => json.asString.getOrElse(json.toString()))
|
||||||
}.recoverAccessErrors
|
}.recoverAccessErrors
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
override def setKey(
|
override def setKey(
|
||||||
key: String,
|
key: String,
|
||||||
value: String
|
value: String
|
||||||
): F[GlobalConfigServiceFailure, Unit] = Sync[F].blockingIO {
|
): F[GlobalConfigServiceFailure, Unit] = Sync[F].blockingOp {
|
||||||
configurationManager.updateConfigRaw(key, Json.fromString(value))
|
configurationManager.updateConfigRaw(key, Json.fromString(value))
|
||||||
}.recoverAccessErrors
|
}.recoverAccessErrors
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
override def deleteKey(key: String): F[GlobalConfigServiceFailure, Unit] =
|
override def deleteKey(key: String): F[GlobalConfigServiceFailure, Unit] =
|
||||||
Sync[F].blockingIO {
|
Sync[F].blockingOp {
|
||||||
configurationManager.removeFromConfig(key)
|
configurationManager.removeFromConfig(key)
|
||||||
}.recoverAccessErrors
|
}.recoverAccessErrors
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getDefaultEnsoVersion: F[GlobalConfigServiceFailure, SemVer] =
|
||||||
|
Sync[F].blockingOp {
|
||||||
|
configurationManager.defaultVersion
|
||||||
|
}.recoverAccessErrors
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def resolveEnsoVersion(
|
||||||
|
ensoVersion: EnsoVersion
|
||||||
|
): F[GlobalConfigServiceFailure, SemVer] = ensoVersion match {
|
||||||
|
case DefaultEnsoVersion => getDefaultEnsoVersion
|
||||||
|
case SemVerEnsoVersion(version) => CovariantFlatMap[F].pure(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Syntax for recovering arbitrary errors into errors describing
|
||||||
|
* configuration access failure.
|
||||||
|
*/
|
||||||
implicit class AccessErrorRecovery[A](fa: F[Throwable, A]) {
|
implicit class AccessErrorRecovery[A](fa: F[Throwable, A]) {
|
||||||
def recoverAccessErrors: F[GlobalConfigServiceFailure, A] = {
|
def recoverAccessErrors: F[GlobalConfigServiceFailure, A] = {
|
||||||
ErrorChannel[F].mapError(fa) { throwable =>
|
ErrorChannel[F].mapError(fa) { throwable =>
|
||||||
@ -47,4 +70,5 @@ class GlobalConfigService[F[+_, +_]: Sync: ErrorChannel](
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package org.enso.projectmanager.service.config
|
package org.enso.projectmanager.service.config
|
||||||
|
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.pkg.EnsoVersion
|
||||||
|
|
||||||
/** A contract for the Global Config Service.
|
/** A contract for the Global Config Service.
|
||||||
*
|
*
|
||||||
* @tparam F a monadic context
|
* @tparam F a monadic context
|
||||||
@ -23,4 +26,19 @@ trait GlobalConfigServiceApi[F[+_, +_]] {
|
|||||||
* If the value was not present already, nothing happens.
|
* If the value was not present already, nothing happens.
|
||||||
*/
|
*/
|
||||||
def deleteKey(key: String): F[GlobalConfigServiceFailure, Unit]
|
def deleteKey(key: String): F[GlobalConfigServiceFailure, Unit]
|
||||||
|
|
||||||
|
/** Returns the default engine version.
|
||||||
|
*
|
||||||
|
* It reads the setting from the config, or if no version is set, falls back
|
||||||
|
* to the latest installed (or latest available if none are installed)
|
||||||
|
* version.
|
||||||
|
*/
|
||||||
|
def getDefaultEnsoVersion: F[GlobalConfigServiceFailure, SemVer]
|
||||||
|
|
||||||
|
/** Resolves an [[EnsoVersion]] which can indicate to use a 'default' version
|
||||||
|
* to a concrete version.
|
||||||
|
*/
|
||||||
|
def resolveEnsoVersion(
|
||||||
|
ensoVersion: EnsoVersion
|
||||||
|
): F[GlobalConfigServiceFailure, SemVer]
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import org.enso.projectmanager.service.ProjectServiceFailure.{
|
|||||||
ComponentRepositoryAccessFailure,
|
ComponentRepositoryAccessFailure,
|
||||||
ComponentUninstallationFailure
|
ComponentUninstallationFailure
|
||||||
}
|
}
|
||||||
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._
|
||||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
import org.enso.runtimeversionmanager.components.ComponentMissingError
|
import org.enso.runtimeversionmanager.components.ComponentMissingError
|
||||||
|
|
||||||
@ -19,9 +20,10 @@ import org.enso.runtimeversionmanager.components.ComponentMissingError
|
|||||||
* @param distributionConfiguration a distribution configuration
|
* @param distributionConfiguration a distribution configuration
|
||||||
*/
|
*/
|
||||||
class RuntimeVersionManagementService[F[+_, +_]: Sync: ErrorChannel](
|
class RuntimeVersionManagementService[F[+_, +_]: Sync: ErrorChannel](
|
||||||
override val distributionConfiguration: DistributionConfiguration
|
distributionConfiguration: DistributionConfiguration
|
||||||
) extends RuntimeVersionManagementServiceApi[F]
|
) extends RuntimeVersionManagementServiceApi[F] {
|
||||||
with RuntimeVersionManagerMixin {
|
|
||||||
|
val factory = RuntimeVersionManagerFactory(distributionConfiguration)
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def installEngine(
|
override def installEngine(
|
||||||
@ -31,11 +33,13 @@ class RuntimeVersionManagementService[F[+_, +_]: Sync: ErrorChannel](
|
|||||||
): F[ProjectServiceFailure, Unit] = {
|
): F[ProjectServiceFailure, Unit] = {
|
||||||
Sync[F]
|
Sync[F]
|
||||||
.blockingOp {
|
.blockingOp {
|
||||||
makeRuntimeVersionManager(
|
factory
|
||||||
progressTracker,
|
.makeRuntimeVersionManager(
|
||||||
allowMissingComponents = true,
|
progressTracker,
|
||||||
allowBrokenComponents = forceInstallBroken
|
allowMissingComponents = true,
|
||||||
).findOrInstallEngine(version)
|
allowBrokenComponents = forceInstallBroken
|
||||||
|
)
|
||||||
|
.findOrInstallEngine(version)
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
.mapRuntimeManagerErrors(throwable =>
|
.mapRuntimeManagerErrors(throwable =>
|
||||||
@ -50,11 +54,13 @@ class RuntimeVersionManagementService[F[+_, +_]: Sync: ErrorChannel](
|
|||||||
): F[ProjectServiceFailure, Unit] = Sync[F]
|
): F[ProjectServiceFailure, Unit] = Sync[F]
|
||||||
.blockingOp {
|
.blockingOp {
|
||||||
try {
|
try {
|
||||||
makeRuntimeVersionManager(
|
factory
|
||||||
progressTracker,
|
.makeRuntimeVersionManager(
|
||||||
allowMissingComponents = false,
|
progressTracker,
|
||||||
allowBrokenComponents = false
|
allowMissingComponents = false,
|
||||||
).uninstallEngine(version)
|
allowBrokenComponents = false
|
||||||
|
)
|
||||||
|
.uninstallEngine(version)
|
||||||
} catch {
|
} catch {
|
||||||
case _: ComponentMissingError =>
|
case _: ComponentMissingError =>
|
||||||
}
|
}
|
||||||
@ -67,7 +73,7 @@ class RuntimeVersionManagementService[F[+_, +_]: Sync: ErrorChannel](
|
|||||||
override def listInstalledEngines()
|
override def listInstalledEngines()
|
||||||
: F[ProjectServiceFailure, Seq[EngineVersion]] = Sync[F]
|
: F[ProjectServiceFailure, Seq[EngineVersion]] = Sync[F]
|
||||||
.blockingOp {
|
.blockingOp {
|
||||||
makeReadOnlyVersionManager().listInstalledEngines().map {
|
factory.makeReadOnlyVersionManager().listInstalledEngines().map {
|
||||||
installedEngine =>
|
installedEngine =>
|
||||||
EngineVersion(installedEngine.version, installedEngine.isMarkedBroken)
|
EngineVersion(installedEngine.version, installedEngine.isMarkedBroken)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package org.enso.projectmanager.service.versionmanagement
|
||||||
|
|
||||||
|
import org.enso.projectmanager.control.effect.ErrorChannel
|
||||||
|
import org.enso.projectmanager.service.ProjectServiceFailure
|
||||||
|
import org.enso.projectmanager.service.ProjectServiceFailure.{
|
||||||
|
BrokenComponentFailure,
|
||||||
|
ComponentInstallationFailure,
|
||||||
|
MissingComponentFailure,
|
||||||
|
ProjectManagerUpgradeRequiredFailure
|
||||||
|
}
|
||||||
|
import org.enso.runtimeversionmanager.components.{
|
||||||
|
BrokenComponentError,
|
||||||
|
ComponentMissingError,
|
||||||
|
ComponentsException,
|
||||||
|
InstallationError,
|
||||||
|
UpgradeRequiredError
|
||||||
|
}
|
||||||
|
|
||||||
|
object RuntimeVersionManagerErrorRecoverySyntax {
|
||||||
|
implicit class ErrorRecovery[F[+_, +_]: ErrorChannel, A](
|
||||||
|
fa: F[Throwable, A]
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** Converts relevant [[ComponentsException]] errors into their counterparts
|
||||||
|
* in the protocol.
|
||||||
|
*
|
||||||
|
* @param mapDefault a mapping that should be used for other errors that do
|
||||||
|
* not have a direct counterpart
|
||||||
|
*/
|
||||||
|
def mapRuntimeManagerErrors(
|
||||||
|
mapDefault: Throwable => ProjectServiceFailure
|
||||||
|
): F[ProjectServiceFailure, A] = ErrorChannel[F].mapError(fa) {
|
||||||
|
case componentsException: ComponentsException =>
|
||||||
|
componentsException match {
|
||||||
|
case InstallationError(message, _) =>
|
||||||
|
ComponentInstallationFailure(message)
|
||||||
|
case BrokenComponentError(message, _) =>
|
||||||
|
BrokenComponentFailure(message)
|
||||||
|
case ComponentMissingError(message, _) =>
|
||||||
|
MissingComponentFailure(message)
|
||||||
|
case upgradeRequired: UpgradeRequiredError =>
|
||||||
|
ProjectManagerUpgradeRequiredFailure(
|
||||||
|
upgradeRequired.expectedVersion
|
||||||
|
)
|
||||||
|
case _ => mapDefault(componentsException)
|
||||||
|
}
|
||||||
|
case other: Throwable =>
|
||||||
|
mapDefault(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package org.enso.projectmanager.service.versionmanagement
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
|
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||||
|
import org.enso.runtimeversionmanager.components._
|
||||||
|
|
||||||
|
/** A helper class that defines methods for creating the
|
||||||
|
* [[RuntimeVersionManager]] based on a
|
||||||
|
* [[DistributionConfiguration]].
|
||||||
|
*/
|
||||||
|
case class RuntimeVersionManagerFactory(
|
||||||
|
distributionConfiguration: DistributionConfiguration
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** Creates a [[RuntimeVersionManager]] that will send
|
||||||
|
* [[ProgressNotification]] to the specified [[ActorRef]] and with the
|
||||||
|
* specified settings for handling missing and broken components.
|
||||||
|
*
|
||||||
|
* @param progressTracker the actor that tracks installation progress/lock
|
||||||
|
* notifications
|
||||||
|
* @param allowMissingComponents if set to true, missing components will be
|
||||||
|
* installed
|
||||||
|
* @param allowBrokenComponents if allowMissingComponents and this flag are
|
||||||
|
* set to true, missing components will be
|
||||||
|
* installed even if they are marked as broken
|
||||||
|
*/
|
||||||
|
def makeRuntimeVersionManager(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
allowMissingComponents: Boolean = false,
|
||||||
|
allowBrokenComponents: Boolean = false
|
||||||
|
): RuntimeVersionManager =
|
||||||
|
distributionConfiguration.makeRuntimeVersionManager(
|
||||||
|
new ControllerInterface(
|
||||||
|
progressTracker = progressTracker,
|
||||||
|
allowMissingComponents = allowMissingComponents,
|
||||||
|
allowBrokenComponents = allowBrokenComponents
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Creates a [[RuntimeVersionManager]] that will send
|
||||||
|
* [[ProgressNotification]] to the specified [[ActorRef]] and with the
|
||||||
|
* specified settings for handling missing and broken components.
|
||||||
|
*
|
||||||
|
* @param progressTracker the actor that tracks installation progress/lock
|
||||||
|
* notifications
|
||||||
|
* @param missingComponentAction specifies how to handle missing components
|
||||||
|
*/
|
||||||
|
def makeRuntimeVersionManager(
|
||||||
|
progressTracker: ActorRef,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): RuntimeVersionManager = {
|
||||||
|
val (missing, broken) = missingComponentAction match {
|
||||||
|
case MissingComponentAction.Fail => (false, false)
|
||||||
|
case MissingComponentAction.Install => (true, false)
|
||||||
|
case MissingComponentAction.ForceInstallBroken => (true, true)
|
||||||
|
}
|
||||||
|
makeRuntimeVersionManager(
|
||||||
|
progressTracker,
|
||||||
|
allowMissingComponents = missing,
|
||||||
|
allowBrokenComponents = broken
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a simple [[RuntimeVersionManager]] that ignores progress (it can
|
||||||
|
* be used when we know that no relevant progress will be reported) and not
|
||||||
|
* allowing to install any components.
|
||||||
|
*
|
||||||
|
* It is useful for simple queries, like listing installed versions.
|
||||||
|
*/
|
||||||
|
def makeReadOnlyVersionManager(): RuntimeVersionManager =
|
||||||
|
distributionConfiguration.makeRuntimeVersionManager(new NoOpInterface)
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
package org.enso.projectmanager.service.versionmanagement
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import org.enso.projectmanager.control.effect.ErrorChannel
|
|
||||||
import org.enso.projectmanager.service.ProjectServiceFailure
|
|
||||||
import org.enso.projectmanager.service.ProjectServiceFailure.{
|
|
||||||
BrokenComponentFailure,
|
|
||||||
ComponentInstallationFailure,
|
|
||||||
MissingComponentFailure,
|
|
||||||
ProjectManagerUpgradeRequiredFailure
|
|
||||||
}
|
|
||||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
|
||||||
import org.enso.runtimeversionmanager.components._
|
|
||||||
|
|
||||||
/** A helper class that defines methods for creating the
|
|
||||||
* [[RuntimeVersionManager]] based on a
|
|
||||||
* [[DistributionConfiguration]].
|
|
||||||
*/
|
|
||||||
trait RuntimeVersionManagerMixin {
|
|
||||||
|
|
||||||
/** The distribution configuration to use. */
|
|
||||||
def distributionConfiguration: DistributionConfiguration
|
|
||||||
|
|
||||||
/** Creates a [[RuntimeVersionManager]] that will send
|
|
||||||
* [[ProgressNotification]] to the specified [[ActorRef]] and with the
|
|
||||||
* specified settings for handling missing and broken components.
|
|
||||||
*/
|
|
||||||
def makeRuntimeVersionManager(
|
|
||||||
progressTracker: ActorRef,
|
|
||||||
allowMissingComponents: Boolean,
|
|
||||||
allowBrokenComponents: Boolean
|
|
||||||
): RuntimeVersionManager =
|
|
||||||
distributionConfiguration.makeRuntimeVersionManager(
|
|
||||||
new ControllerInterface(
|
|
||||||
progressTracker = progressTracker,
|
|
||||||
allowMissingComponents = allowMissingComponents,
|
|
||||||
allowBrokenComponents = allowBrokenComponents
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Creates a simple [[RuntimeVersionManager]] that ignores progress (it can
|
|
||||||
* be used when we know that no relevant progress will be reported) and not
|
|
||||||
* allowing to install any components.
|
|
||||||
*
|
|
||||||
* It is useful for simple queries, like listing installed versions.
|
|
||||||
*/
|
|
||||||
def makeReadOnlyVersionManager(): RuntimeVersionManager =
|
|
||||||
distributionConfiguration.makeRuntimeVersionManager(new NoOpInterface)
|
|
||||||
|
|
||||||
implicit class ErrorRecovery[F[+_, +_]: ErrorChannel, A](
|
|
||||||
fa: F[Throwable, A]
|
|
||||||
) {
|
|
||||||
|
|
||||||
/** Converts relevant [[ComponentsException]] errors into their counterparts
|
|
||||||
* in the protocol.
|
|
||||||
*/
|
|
||||||
def mapRuntimeManagerErrors(
|
|
||||||
wrapDefault: Throwable => ProjectServiceFailure
|
|
||||||
): F[ProjectServiceFailure, A] = ErrorChannel[F].mapError(fa) {
|
|
||||||
case componentsException: ComponentsException =>
|
|
||||||
componentsException match {
|
|
||||||
case InstallationError(message, _) =>
|
|
||||||
ComponentInstallationFailure(message)
|
|
||||||
case BrokenComponentError(message, _) =>
|
|
||||||
BrokenComponentFailure(message)
|
|
||||||
case ComponentMissingError(message, _) =>
|
|
||||||
MissingComponentFailure(message)
|
|
||||||
case upgradeRequired: UpgradeRequiredError =>
|
|
||||||
ProjectManagerUpgradeRequiredFailure(
|
|
||||||
upgradeRequired.expectedVersion
|
|
||||||
)
|
|
||||||
case _ => wrapDefault(componentsException)
|
|
||||||
}
|
|
||||||
case other: Throwable =>
|
|
||||||
wrapDefault(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,6 +16,7 @@ import org.enso.runtimeversionmanager.releases.engine.{
|
|||||||
EngineRepository
|
EngineRepository
|
||||||
}
|
}
|
||||||
import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider
|
import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider
|
||||||
|
import org.enso.runtimeversionmanager.runner.JVMSettings
|
||||||
|
|
||||||
/** Default distribution configuration to use for the Project Manager in
|
/** Default distribution configuration to use for the Project Manager in
|
||||||
* production.
|
* production.
|
||||||
@ -27,13 +28,13 @@ import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider
|
|||||||
object DefaultDistributionConfiguration extends DistributionConfiguration {
|
object DefaultDistributionConfiguration extends DistributionConfiguration {
|
||||||
|
|
||||||
/** The default [[Environment]] implementation, with no overrides. */
|
/** The default [[Environment]] implementation, with no overrides. */
|
||||||
object DefaultEnvironment extends Environment
|
val environment: Environment = new Environment {}
|
||||||
|
|
||||||
// TODO [RW, AO] should the PM support portable distributions?
|
// TODO [RW, AO] should the PM support portable distributions?
|
||||||
// If so, where will be the project-manager binary located with respect to
|
// If so, where will be the project-manager binary located with respect to
|
||||||
// the distribution root?
|
// the distribution root?
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
lazy val distributionManager = new DistributionManager(DefaultEnvironment)
|
lazy val distributionManager = new DistributionManager(environment)
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
lazy val lockManager = new FileLockManager(distributionManager.paths.locks)
|
lazy val lockManager = new FileLockManager(distributionManager.paths.locks)
|
||||||
@ -63,4 +64,10 @@ object DefaultDistributionConfiguration extends DistributionConfiguration {
|
|||||||
engineReleaseProvider = engineReleaseProvider,
|
engineReleaseProvider = engineReleaseProvider,
|
||||||
runtimeReleaseProvider = runtimeReleaseProvider
|
runtimeReleaseProvider = runtimeReleaseProvider
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def defaultJVMSettings: JVMSettings = JVMSettings.default
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def shouldDiscardChildOutput: Boolean = false
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.enso.projectmanager.versionmanagement
|
package org.enso.projectmanager.versionmanagement
|
||||||
|
|
||||||
|
import org.enso.runtimeversionmanager.Environment
|
||||||
import org.enso.runtimeversionmanager.components.{
|
import org.enso.runtimeversionmanager.components.{
|
||||||
RuntimeVersionManagementUserInterface,
|
RuntimeVersionManagementUserInterface,
|
||||||
RuntimeVersionManager
|
RuntimeVersionManager
|
||||||
@ -11,6 +12,7 @@ import org.enso.runtimeversionmanager.distribution.{
|
|||||||
import org.enso.runtimeversionmanager.locking.ResourceManager
|
import org.enso.runtimeversionmanager.locking.ResourceManager
|
||||||
import org.enso.runtimeversionmanager.releases.ReleaseProvider
|
import org.enso.runtimeversionmanager.releases.ReleaseProvider
|
||||||
import org.enso.runtimeversionmanager.releases.engine.EngineRelease
|
import org.enso.runtimeversionmanager.releases.engine.EngineRelease
|
||||||
|
import org.enso.runtimeversionmanager.runner.JVMSettings
|
||||||
|
|
||||||
/** Specifies the configuration of project manager's distribution.
|
/** Specifies the configuration of project manager's distribution.
|
||||||
*
|
*
|
||||||
@ -20,6 +22,9 @@ import org.enso.runtimeversionmanager.releases.engine.EngineRelease
|
|||||||
*/
|
*/
|
||||||
trait DistributionConfiguration {
|
trait DistributionConfiguration {
|
||||||
|
|
||||||
|
/** An [[Environment]] instance. */
|
||||||
|
def environment: Environment
|
||||||
|
|
||||||
/** A [[DistributionManager]] instance. */
|
/** A [[DistributionManager]] instance. */
|
||||||
def distributionManager: DistributionManager
|
def distributionManager: DistributionManager
|
||||||
|
|
||||||
@ -39,4 +44,19 @@ trait DistributionConfiguration {
|
|||||||
def makeRuntimeVersionManager(
|
def makeRuntimeVersionManager(
|
||||||
userInterface: RuntimeVersionManagementUserInterface
|
userInterface: RuntimeVersionManagementUserInterface
|
||||||
): RuntimeVersionManager
|
): RuntimeVersionManager
|
||||||
|
|
||||||
|
/** Default set of JVM settings to use when launching the runner.
|
||||||
|
*
|
||||||
|
* This is exposed mostly for ease of overriding the settings in tests.
|
||||||
|
*/
|
||||||
|
def defaultJVMSettings: JVMSettings
|
||||||
|
|
||||||
|
/** Specifies if output of the child Language Server process should be ignored
|
||||||
|
* or piped to parent's streams.
|
||||||
|
*
|
||||||
|
* This option is used to easily turn off logging in tests.
|
||||||
|
*
|
||||||
|
* TODO [RW] It will likely become obsolete once #1151 (or #1144) is done.
|
||||||
|
*/
|
||||||
|
def shouldDiscardChildOutput: Boolean
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ project-manager {
|
|||||||
io-timeout = 5 seconds
|
io-timeout = 5 seconds
|
||||||
request-timeout = 10 seconds
|
request-timeout = 10 seconds
|
||||||
boot-timeout = 30 seconds
|
boot-timeout = 30 seconds
|
||||||
shutdown-timeout = 10 seconds
|
shutdown-timeout = 20 seconds
|
||||||
socket-close-timeout = 2 seconds
|
socket-close-timeout = 2 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<!-- encoders are assigned the type
|
|
||||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%-15thread] %-5level %logger{36} %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="ERROR"/>
|
|
||||||
<logger name="slick" level="INFO"/>
|
|
||||||
<logger name="slick.compiler" level="INFO"/>
|
|
||||||
|
|
||||||
<root level="ERROR">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
@ -1,19 +1,26 @@
|
|||||||
package org.enso.projectmanager
|
package org.enso.projectmanager
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Files
|
import java.nio.file.{Files, Path}
|
||||||
import java.time.{OffsetDateTime, ZoneOffset}
|
import java.time.{OffsetDateTime, ZoneOffset}
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.testkit.TestActors.blackholeProps
|
||||||
import akka.testkit._
|
import akka.testkit._
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.parser.parse
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.enso.jsonrpc.test.JsonRpcServerTestKit
|
import org.enso.jsonrpc.test.JsonRpcServerTestKit
|
||||||
import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
|
import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
|
||||||
|
import org.enso.loggingservice.printers.StderrPrinterWithColors
|
||||||
|
import org.enso.loggingservice.{LogLevel, LoggerMode, LoggingServiceManager}
|
||||||
import org.enso.projectmanager.boot.Globals.{ConfigFilename, ConfigNamespace}
|
import org.enso.projectmanager.boot.Globals.{ConfigFilename, ConfigNamespace}
|
||||||
import org.enso.projectmanager.boot.configuration._
|
import org.enso.projectmanager.boot.configuration._
|
||||||
import org.enso.projectmanager.control.effect.ZioEnvExec
|
import org.enso.projectmanager.control.effect.ZioEnvExec
|
||||||
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem
|
import org.enso.projectmanager.infrastructure.file.BlockingFileSystem
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.{
|
import org.enso.projectmanager.infrastructure.languageserver.{
|
||||||
|
ExecutorWithUnlimitedPool,
|
||||||
LanguageServerGatewayImpl,
|
LanguageServerGatewayImpl,
|
||||||
LanguageServerRegistry,
|
LanguageServerRegistry,
|
||||||
ShutdownHookActivator
|
ShutdownHookActivator
|
||||||
@ -26,18 +33,27 @@ import org.enso.projectmanager.protocol.{
|
|||||||
}
|
}
|
||||||
import org.enso.projectmanager.service.config.GlobalConfigService
|
import org.enso.projectmanager.service.config.GlobalConfigService
|
||||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementService
|
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementService
|
||||||
import org.enso.projectmanager.service.{MonadicProjectValidator, ProjectService}
|
import org.enso.projectmanager.service.{
|
||||||
|
MonadicProjectValidator,
|
||||||
|
ProjectCreationService,
|
||||||
|
ProjectService
|
||||||
|
}
|
||||||
import org.enso.projectmanager.test.{ObservableGenerator, ProgrammableClock}
|
import org.enso.projectmanager.test.{ObservableGenerator, ProgrammableClock}
|
||||||
|
import org.enso.runtimeversionmanager.OS
|
||||||
import org.enso.runtimeversionmanager.test.{DropLogs, FakeReleases}
|
import org.enso.runtimeversionmanager.test.{DropLogs, FakeReleases}
|
||||||
|
import org.scalatest.BeforeAndAfterAll
|
||||||
import pureconfig.ConfigSource
|
import pureconfig.ConfigSource
|
||||||
import pureconfig.generic.auto._
|
import pureconfig.generic.auto._
|
||||||
import zio.interop.catz.core._
|
import zio.interop.catz.core._
|
||||||
import zio.{Runtime, Semaphore, ZEnv, ZIO}
|
import zio.{Runtime, Semaphore, ZEnv, ZIO}
|
||||||
|
|
||||||
import scala.concurrent.{Await, Future}
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.{Await, Future}
|
||||||
|
|
||||||
class BaseServerSpec extends JsonRpcServerTestKit with DropLogs {
|
class BaseServerSpec
|
||||||
|
extends JsonRpcServerTestKit
|
||||||
|
with DropLogs
|
||||||
|
with BeforeAndAfterAll {
|
||||||
|
|
||||||
override def protocol: Protocol = JsonRpc.protocol
|
override def protocol: Protocol = JsonRpc.protocol
|
||||||
|
|
||||||
@ -97,10 +113,25 @@ class BaseServerSpec extends JsonRpcServerTestKit with DropLogs {
|
|||||||
|
|
||||||
lazy val projectValidator = new MonadicProjectValidator[ZIO[ZEnv, *, *]]()
|
lazy val projectValidator = new MonadicProjectValidator[ZIO[ZEnv, *, *]]()
|
||||||
|
|
||||||
|
lazy val distributionConfiguration =
|
||||||
|
TestDistributionConfiguration(
|
||||||
|
distributionRoot = testDistributionRoot.toPath,
|
||||||
|
engineReleaseProvider = FakeReleases.engineReleaseProvider,
|
||||||
|
runtimeReleaseProvider = FakeReleases.runtimeReleaseProvider,
|
||||||
|
discardChildOutput = !debugChildLogs
|
||||||
|
)
|
||||||
|
|
||||||
lazy val languageServerRegistry =
|
lazy val languageServerRegistry =
|
||||||
system.actorOf(
|
system.actorOf(
|
||||||
LanguageServerRegistry
|
LanguageServerRegistry
|
||||||
.props(netConfig, bootloaderConfig, supervisionConfig, timeoutConfig)
|
.props(
|
||||||
|
netConfig,
|
||||||
|
bootloaderConfig,
|
||||||
|
supervisionConfig,
|
||||||
|
timeoutConfig,
|
||||||
|
distributionConfiguration,
|
||||||
|
ExecutorWithUnlimitedPool
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val shutdownHookActivator =
|
lazy val shutdownHookActivator =
|
||||||
@ -114,27 +145,26 @@ class BaseServerSpec extends JsonRpcServerTestKit with DropLogs {
|
|||||||
timeoutConfig
|
timeoutConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val projectService =
|
lazy val projectCreationService =
|
||||||
new ProjectService[ZIO[ZEnv, +*, +*]](
|
new ProjectCreationService[ZIO[ZEnv, +*, +*]](distributionConfiguration)
|
||||||
projectValidator,
|
|
||||||
projectRepository,
|
|
||||||
new Slf4jLogging[ZIO[ZEnv, +*, +*]],
|
|
||||||
testClock,
|
|
||||||
gen,
|
|
||||||
languageServerGateway
|
|
||||||
)
|
|
||||||
|
|
||||||
lazy val distributionConfiguration =
|
|
||||||
TestDistributionConfiguration(
|
|
||||||
distributionRoot = testDistributionRoot.toPath,
|
|
||||||
engineReleaseProvider = FakeReleases.engineReleaseProvider,
|
|
||||||
runtimeReleaseProvider = FakeReleases.runtimeReleaseProvider
|
|
||||||
)
|
|
||||||
|
|
||||||
lazy val globalConfigService = new GlobalConfigService[ZIO[ZEnv, +*, +*]](
|
lazy val globalConfigService = new GlobalConfigService[ZIO[ZEnv, +*, +*]](
|
||||||
distributionConfiguration
|
distributionConfiguration
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lazy val projectService =
|
||||||
|
new ProjectService[ZIO[ZEnv, +*, +*]](
|
||||||
|
projectValidator,
|
||||||
|
projectRepository,
|
||||||
|
projectCreationService,
|
||||||
|
globalConfigService,
|
||||||
|
new Slf4jLogging[ZIO[ZEnv, +*, +*]],
|
||||||
|
testClock,
|
||||||
|
gen,
|
||||||
|
languageServerGateway,
|
||||||
|
distributionConfiguration
|
||||||
|
)
|
||||||
|
|
||||||
lazy val runtimeVersionManagementService =
|
lazy val runtimeVersionManagementService =
|
||||||
new RuntimeVersionManagementService[ZIO[ZEnv, +*, +*]](
|
new RuntimeVersionManagementService[ZIO[ZEnv, +*, +*]](
|
||||||
distributionConfiguration
|
distributionConfiguration
|
||||||
@ -150,9 +180,141 @@ class BaseServerSpec extends JsonRpcServerTestKit with DropLogs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Can be used to avoid deleting the project's root. */
|
||||||
|
val deleteProjectsRootAfterEachTest = true
|
||||||
|
|
||||||
override def afterEach(): Unit = {
|
override def afterEach(): Unit = {
|
||||||
super.afterEach()
|
super.afterEach()
|
||||||
|
|
||||||
|
if (deleteProjectsRootAfterEachTest)
|
||||||
|
FileUtils.deleteQuietly(testProjectsRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def afterAll(): Unit = {
|
||||||
|
super.afterAll()
|
||||||
|
|
||||||
FileUtils.deleteQuietly(testProjectsRoot)
|
FileUtils.deleteQuietly(testProjectsRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tests can override this value to request a specific engine version to be
|
||||||
|
* preinstalled when running the suite.
|
||||||
|
*/
|
||||||
|
val engineToInstall: Option[SemVer] = None
|
||||||
|
|
||||||
|
/** Tests can override this to set up a logging service that will print debug
|
||||||
|
* logs.
|
||||||
|
*/
|
||||||
|
val debugLogs: Boolean = false
|
||||||
|
|
||||||
|
/** Tests can override this to allow child process output to be displayed. */
|
||||||
|
val debugChildLogs: Boolean = false
|
||||||
|
|
||||||
|
override def beforeAll(): Unit = {
|
||||||
|
super.beforeAll()
|
||||||
|
|
||||||
|
if (debugLogs) {
|
||||||
|
LoggingServiceManager.setup(
|
||||||
|
LoggerMode.Local(
|
||||||
|
Seq(StderrPrinterWithColors.colorPrinterIfAvailable(true))
|
||||||
|
),
|
||||||
|
LogLevel.Trace
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
engineToInstall.foreach(preInstallEngine)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This is a temporary solution to ensure that a valid engine distribution is
|
||||||
|
* preinstalled.
|
||||||
|
*
|
||||||
|
* In the future the fake release mechanism can be properly updated to allow
|
||||||
|
* for this kind of configuration without special logic.
|
||||||
|
*/
|
||||||
|
def preInstallEngine(version: SemVer): Unit = {
|
||||||
|
val os = OS.operatingSystem.configName
|
||||||
|
val ext = if (OS.isWindows) "zip" else "tar.gz"
|
||||||
|
val arch = OS.architecture
|
||||||
|
val path = FakeReleases.releaseRoot
|
||||||
|
.resolve("enso")
|
||||||
|
.resolve(s"enso-$version")
|
||||||
|
.resolve(s"enso-engine-$version-$os-$arch.$ext")
|
||||||
|
.resolve(s"enso-$version")
|
||||||
|
.resolve("component")
|
||||||
|
val root = Path.of("../../../").toAbsolutePath.normalize
|
||||||
|
FileUtils.copyFile(
|
||||||
|
root.resolve("runner.jar").toFile,
|
||||||
|
path.resolve("runner.jar").toFile
|
||||||
|
)
|
||||||
|
FileUtils.copyFile(
|
||||||
|
root.resolve("runtime.jar").toFile,
|
||||||
|
path.resolve("runtime.jar").toFile
|
||||||
|
)
|
||||||
|
|
||||||
|
val blackhole = system.actorOf(blackholeProps)
|
||||||
|
val installAction = runtimeVersionManagementService.installEngine(
|
||||||
|
blackhole,
|
||||||
|
version,
|
||||||
|
forceInstallBroken = false
|
||||||
|
)
|
||||||
|
Runtime.default.unsafeRun(installAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
def uninstallEngine(version: SemVer): Unit = {
|
||||||
|
val blackhole = system.actorOf(blackholeProps)
|
||||||
|
val action = runtimeVersionManagementService.uninstallEngine(
|
||||||
|
blackhole,
|
||||||
|
version
|
||||||
|
)
|
||||||
|
Runtime.default.unsafeRun(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit class ClientSyntax(client: WsTestClient) {
|
||||||
|
def expectTaskStarted(
|
||||||
|
timeout: FiniteDuration = 20.seconds.dilated
|
||||||
|
): Unit = {
|
||||||
|
inside(parse(client.expectMessage(timeout))) { case Right(json) =>
|
||||||
|
getMethod(json) shouldEqual Some("task/started")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getMethod(json: Json): Option[String] = for {
|
||||||
|
obj <- json.asObject
|
||||||
|
method <- obj("method").flatMap(_.asString)
|
||||||
|
} yield method
|
||||||
|
|
||||||
|
def expectJsonIgnoring(
|
||||||
|
shouldIgnore: Json => Boolean,
|
||||||
|
timeout: FiniteDuration = 20.seconds.dilated
|
||||||
|
): Json = {
|
||||||
|
inside(parse(client.expectMessage(timeout))) { case Right(json) =>
|
||||||
|
if (shouldIgnore(json)) expectJsonIgnoring(shouldIgnore, timeout)
|
||||||
|
else json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def expectError(
|
||||||
|
expectedCode: Int,
|
||||||
|
timeout: FiniteDuration = 10.seconds.dilated
|
||||||
|
): Unit = {
|
||||||
|
withClue("Response should be an error: ") {
|
||||||
|
inside(parse(client.expectMessage(timeout))) { case Right(json) =>
|
||||||
|
val code = for {
|
||||||
|
obj <- json.asObject
|
||||||
|
error <- obj("error").flatMap(_.asObject)
|
||||||
|
code <- error("code").flatMap(_.asNumber).flatMap(_.toInt)
|
||||||
|
} yield code
|
||||||
|
code shouldEqual Some(expectedCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def expectJsonAfterSomeProgress(
|
||||||
|
json: Json,
|
||||||
|
timeout: FiniteDuration = 10.seconds.dilated
|
||||||
|
): Unit =
|
||||||
|
expectJsonIgnoring(
|
||||||
|
json => getMethod(json).exists(_.startsWith("task/")),
|
||||||
|
timeout
|
||||||
|
) shouldEqual json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.enso.projectmanager
|
|||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.testkit.TestDuration
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
import io.circe.literal._
|
import io.circe.literal._
|
||||||
@ -58,7 +59,7 @@ trait ProjectManagementOps { this: BaseServerSpec =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
val Right(openReply) = parse(client.expectMessage(10.seconds))
|
val Right(openReply) = parse(client.expectMessage(10.seconds.dilated))
|
||||||
val socket = for {
|
val socket = for {
|
||||||
result <- openReply.hcursor.downExpectedField("result")
|
result <- openReply.hcursor.downExpectedField("result")
|
||||||
addr <- result.downExpectedField("languageServerJsonAddress")
|
addr <- result.downExpectedField("languageServerJsonAddress")
|
||||||
@ -81,13 +82,16 @@ trait ProjectManagementOps { this: BaseServerSpec =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
client.expectJson(json"""
|
client.expectJson(
|
||||||
|
json"""
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
"id":0,
|
"id":0,
|
||||||
"result": null
|
"result": null
|
||||||
}
|
}
|
||||||
""")
|
""",
|
||||||
|
10.seconds.dilated
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def deleteProject(
|
def deleteProject(
|
||||||
|
@ -25,12 +25,14 @@ import org.enso.runtimeversionmanager.releases.{
|
|||||||
ReleaseProvider,
|
ReleaseProvider,
|
||||||
SimpleReleaseProvider
|
SimpleReleaseProvider
|
||||||
}
|
}
|
||||||
|
import org.enso.runtimeversionmanager.runner.{JVMSettings, JavaCommand}
|
||||||
import org.enso.runtimeversionmanager.test.{
|
import org.enso.runtimeversionmanager.test.{
|
||||||
FakeEnvironment,
|
FakeEnvironment,
|
||||||
HasTestDirectory,
|
HasTestDirectory,
|
||||||
TestLocalLockManager
|
TestLocalLockManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import scala.jdk.OptionConverters.RichOptional
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
/** A distribution configuration for use in tests.
|
/** A distribution configuration for use in tests.
|
||||||
@ -39,20 +41,23 @@ import scala.util.{Failure, Success, Try}
|
|||||||
* within some temporary directory
|
* within some temporary directory
|
||||||
* @param engineReleaseProvider provider of (fake) engine releases
|
* @param engineReleaseProvider provider of (fake) engine releases
|
||||||
* @param runtimeReleaseProvider provider of (fake) Graal releases
|
* @param runtimeReleaseProvider provider of (fake) Graal releases
|
||||||
|
* @param discardChildOutput specifies if input of launched runner processes
|
||||||
|
* should be ignored
|
||||||
*/
|
*/
|
||||||
class TestDistributionConfiguration(
|
class TestDistributionConfiguration(
|
||||||
distributionRoot: Path,
|
distributionRoot: Path,
|
||||||
override val engineReleaseProvider: ReleaseProvider[EngineRelease],
|
override val engineReleaseProvider: ReleaseProvider[EngineRelease],
|
||||||
runtimeReleaseProvider: GraalVMRuntimeReleaseProvider
|
runtimeReleaseProvider: GraalVMRuntimeReleaseProvider,
|
||||||
|
discardChildOutput: Boolean
|
||||||
) extends DistributionConfiguration
|
) extends DistributionConfiguration
|
||||||
with FakeEnvironment
|
with FakeEnvironment
|
||||||
with HasTestDirectory {
|
with HasTestDirectory {
|
||||||
|
|
||||||
def getTestDirectory: Path = distributionRoot
|
def getTestDirectory: Path = distributionRoot
|
||||||
|
|
||||||
lazy val distributionManager = new DistributionManager(
|
lazy val environment = fakeInstalledEnvironment()
|
||||||
fakeInstalledEnvironment()
|
|
||||||
)
|
lazy val distributionManager = new DistributionManager(environment)
|
||||||
|
|
||||||
lazy val lockManager = new TestLocalLockManager
|
lazy val lockManager = new TestLocalLockManager
|
||||||
|
|
||||||
@ -71,36 +76,66 @@ class TestDistributionConfiguration(
|
|||||||
engineReleaseProvider = engineReleaseProvider,
|
engineReleaseProvider = engineReleaseProvider,
|
||||||
runtimeReleaseProvider = runtimeReleaseProvider
|
runtimeReleaseProvider = runtimeReleaseProvider
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
object TestDistributionConfiguration {
|
/** JVM settings that will force to use the same JVM that we are running.
|
||||||
def withoutReleases(distributionRoot: Path): TestDistributionConfiguration = {
|
*
|
||||||
val noReleaseProvider = new SimpleReleaseProvider {
|
* This is done to avoiding downloading GraalVM in tests (that would be far
|
||||||
override def releaseForTag(tag: String): Try[Release] = Failure(
|
* too slow) and to ensure that a GraalVM instance is selected, regardless of
|
||||||
new IllegalStateException(
|
* the default JVM set in the current environment.
|
||||||
"This provider does not support fetching releases."
|
*/
|
||||||
)
|
override def defaultJVMSettings: JVMSettings = {
|
||||||
)
|
val currentProcess =
|
||||||
|
ProcessHandle.current().info().command().toScala.getOrElse("java")
|
||||||
override def listReleases(): Try[Seq[Release]] = Success(Seq())
|
val javaCommand = JavaCommand(currentProcess, None)
|
||||||
}
|
new JVMSettings(
|
||||||
|
javaCommandOverride = Some(javaCommand),
|
||||||
new TestDistributionConfiguration(
|
jvmOptions = Seq()
|
||||||
distributionRoot = distributionRoot,
|
|
||||||
engineReleaseProvider = new EngineReleaseProvider(noReleaseProvider),
|
|
||||||
runtimeReleaseProvider = new GraalCEReleaseProvider(noReleaseProvider)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def shouldDiscardChildOutput: Boolean = discardChildOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
object TestDistributionConfiguration {
|
||||||
|
|
||||||
|
/** Creates a [[TestDistributionConfiguration]] with repositories that do not
|
||||||
|
* have any available releases.
|
||||||
|
*/
|
||||||
|
def withoutReleases(
|
||||||
|
distributionRoot: Path,
|
||||||
|
discardChildOutput: Boolean
|
||||||
|
): TestDistributionConfiguration = {
|
||||||
|
val noReleaseProvider = new NoReleaseProvider
|
||||||
|
new TestDistributionConfiguration(
|
||||||
|
distributionRoot = distributionRoot,
|
||||||
|
engineReleaseProvider = new EngineReleaseProvider(noReleaseProvider),
|
||||||
|
runtimeReleaseProvider = new GraalCEReleaseProvider(noReleaseProvider),
|
||||||
|
discardChildOutput
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a [[TestDistributionConfiguration]] instance. */
|
||||||
def apply(
|
def apply(
|
||||||
distributionRoot: Path,
|
distributionRoot: Path,
|
||||||
engineReleaseProvider: ReleaseProvider[EngineRelease],
|
engineReleaseProvider: ReleaseProvider[EngineRelease],
|
||||||
runtimeReleaseProvider: GraalVMRuntimeReleaseProvider
|
runtimeReleaseProvider: GraalVMRuntimeReleaseProvider,
|
||||||
|
discardChildOutput: Boolean
|
||||||
): TestDistributionConfiguration =
|
): TestDistributionConfiguration =
|
||||||
new TestDistributionConfiguration(
|
new TestDistributionConfiguration(
|
||||||
distributionRoot,
|
distributionRoot,
|
||||||
engineReleaseProvider,
|
engineReleaseProvider,
|
||||||
runtimeReleaseProvider
|
runtimeReleaseProvider,
|
||||||
|
discardChildOutput
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** A [[SimpleReleaseProvider]] that has no releases. */
|
||||||
|
private class NoReleaseProvider extends SimpleReleaseProvider {
|
||||||
|
override def releaseForTag(tag: String): Try[Release] = Failure(
|
||||||
|
new IllegalStateException(
|
||||||
|
"This provider does not support fetching releases."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override def listReleases(): Try[Seq[Release]] = Success(Seq())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package org.enso.projectmanager.infrastructure.languageserver
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
|
||||||
|
import akka.testkit.TestDuration
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.projectmanager.test.Net._
|
import org.enso.projectmanager.test.Net._
|
||||||
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
||||||
import org.enso.testkit.FlakySpec
|
import org.enso.testkit.{FlakySpec, RetrySpec}
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
@ -10,11 +12,14 @@ import scala.concurrent.duration._
|
|||||||
class LanguageServerGatewaySpec
|
class LanguageServerGatewaySpec
|
||||||
extends BaseServerSpec
|
extends BaseServerSpec
|
||||||
with FlakySpec
|
with FlakySpec
|
||||||
with ProjectManagementOps {
|
with ProjectManagementOps
|
||||||
|
with RetrySpec {
|
||||||
|
|
||||||
|
override val engineToInstall = Some(SemVer(0, 0, 1))
|
||||||
|
|
||||||
"A language server service" must {
|
"A language server service" must {
|
||||||
|
|
||||||
"kill all running language servers" ignore {
|
"kill all running language servers" taggedAs Retry ignore {
|
||||||
implicit val client = new WsTestClient(address)
|
implicit val client = new WsTestClient(address)
|
||||||
val fooId = createProject("foo")
|
val fooId = createProject("foo")
|
||||||
val barId = createProject("bar")
|
val barId = createProject("bar")
|
||||||
@ -27,7 +32,7 @@ class LanguageServerGatewaySpec
|
|||||||
tryConnect(bazSocket).isRight shouldBe true
|
tryConnect(bazSocket).isRight shouldBe true
|
||||||
//when
|
//when
|
||||||
val future = exec.exec(languageServerGateway.killAllServers())
|
val future = exec.exec(languageServerGateway.killAllServers())
|
||||||
Await.result(future, 20.seconds)
|
Await.result(future, 30.seconds.dilated)
|
||||||
//then
|
//then
|
||||||
tryConnect(fooSocket).isLeft shouldBe true
|
tryConnect(fooSocket).isLeft shouldBe true
|
||||||
tryConnect(barSocket).isLeft shouldBe true
|
tryConnect(barSocket).isLeft shouldBe true
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package org.enso.projectmanager.infrastructure.languageserver
|
package org.enso.projectmanager.infrastructure.languageserver
|
||||||
|
|
||||||
import java.util.UUID
|
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||||
|
import akka.testkit.{ImplicitSender, TestActor, TestKit, TestProbe}
|
||||||
import akka.actor.{ActorSystem, Props}
|
|
||||||
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
|
|
||||||
import com.miguno.akka.testing.VirtualTime
|
import com.miguno.akka.testing.VirtualTime
|
||||||
import org.enso.languageserver.boot.LifecycleComponent.ComponentRestarted
|
|
||||||
import org.enso.languageserver.boot.{LanguageServerConfig, LifecycleComponent}
|
|
||||||
import org.enso.projectmanager.boot.configuration.SupervisionConfig
|
import org.enso.projectmanager.boot.configuration.SupervisionConfig
|
||||||
import org.enso.projectmanager.infrastructure.http.AkkaBasedWebSocketConnectionFactory
|
import org.enso.projectmanager.infrastructure.http.AkkaBasedWebSocketConnectionFactory
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerController.ServerDied
|
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerBootLoader.ServerBooted
|
||||||
import org.enso.projectmanager.infrastructure.languageserver.ProgrammableWebSocketServer.{
|
import org.enso.projectmanager.infrastructure.languageserver.ProgrammableWebSocketServer.{
|
||||||
Reject,
|
Reject,
|
||||||
ReplyWith
|
ReplyWith
|
||||||
@ -17,14 +13,11 @@ import org.enso.projectmanager.infrastructure.languageserver.ProgrammableWebSock
|
|||||||
import org.enso.projectmanager.infrastructure.languageserver.StepParent.ChildTerminated
|
import org.enso.projectmanager.infrastructure.languageserver.StepParent.ChildTerminated
|
||||||
import org.enso.projectmanager.infrastructure.net.Tcp
|
import org.enso.projectmanager.infrastructure.net.Tcp
|
||||||
import org.enso.testkit.FlakySpec
|
import org.enso.testkit.FlakySpec
|
||||||
import org.mockito.BDDMockito._
|
|
||||||
import org.mockito.Mockito._
|
|
||||||
import org.mockito.MockitoSugar
|
import org.mockito.MockitoSugar
|
||||||
import org.scalatest.BeforeAndAfterAll
|
import org.scalatest.BeforeAndAfterAll
|
||||||
import org.scalatest.flatspec.AnyFlatSpecLike
|
import org.scalatest.flatspec.AnyFlatSpecLike
|
||||||
import org.scalatest.matchers.must.Matchers
|
import org.scalatest.matchers.must.Matchers
|
||||||
|
|
||||||
import scala.concurrent.Future
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class LanguageServerSupervisorSpec
|
class LanguageServerSupervisorSpec
|
||||||
@ -55,7 +48,7 @@ class LanguageServerSupervisorSpec
|
|||||||
virtualTimeAdvances(testHeartbeatInterval / 2)
|
virtualTimeAdvances(testHeartbeatInterval / 2)
|
||||||
}
|
}
|
||||||
//then
|
//then
|
||||||
`then`(serverComponent.restart()).shouldHaveNoInteractions()
|
processManagerProbe.expectNoMessage()
|
||||||
//teardown
|
//teardown
|
||||||
parent ! GracefulStop
|
parent ! GracefulStop
|
||||||
parentProbe.expectMsg(ChildTerminated)
|
parentProbe.expectMsg(ChildTerminated)
|
||||||
@ -65,8 +58,6 @@ class LanguageServerSupervisorSpec
|
|||||||
|
|
||||||
it should "restart server when pong message doesn't arrive on time" taggedAs Flaky in new TestCtx {
|
it should "restart server when pong message doesn't arrive on time" taggedAs Flaky in new TestCtx {
|
||||||
//given
|
//given
|
||||||
when(serverComponent.restart())
|
|
||||||
.thenReturn(Future.successful(ComponentRestarted))
|
|
||||||
val probe = TestProbe()
|
val probe = TestProbe()
|
||||||
@volatile var pingCount = 0
|
@volatile var pingCount = 0
|
||||||
fakeServer.withBehaviour { case ping @ PingMatcher(requestId) =>
|
fakeServer.withBehaviour { case ping @ PingMatcher(requestId) =>
|
||||||
@ -84,7 +75,7 @@ class LanguageServerSupervisorSpec
|
|||||||
//when
|
//when
|
||||||
virtualTimeAdvances(testInitialDelay)
|
virtualTimeAdvances(testInitialDelay)
|
||||||
(1 to 2).foreach { _ =>
|
(1 to 2).foreach { _ =>
|
||||||
verifyNoInteractions(serverComponent)
|
processManagerProbe.expectNoMessage()
|
||||||
probe.expectMsgPF() { case PingMatcher(_) => () }
|
probe.expectMsgPF() { case PingMatcher(_) => () }
|
||||||
virtualTimeAdvances(testHeartbeatInterval / 2)
|
virtualTimeAdvances(testHeartbeatInterval / 2)
|
||||||
probe.expectNoMessage()
|
probe.expectNoMessage()
|
||||||
@ -92,10 +83,11 @@ class LanguageServerSupervisorSpec
|
|||||||
}
|
}
|
||||||
probe.expectMsgPF() { case PingMatcher(_) => () }
|
probe.expectMsgPF() { case PingMatcher(_) => () }
|
||||||
virtualTimeAdvances(testHeartbeatTimeout)
|
virtualTimeAdvances(testHeartbeatTimeout)
|
||||||
verify(serverComponent, timeout(VerificationTimeout).times(1)).restart()
|
processManagerProbe.expectMsg(Restart)
|
||||||
|
restartRequests mustEqual 1
|
||||||
virtualTimeAdvances(testInitialDelay)
|
virtualTimeAdvances(testInitialDelay)
|
||||||
(1 to 2).foreach { _ =>
|
(1 to 2).foreach { _ =>
|
||||||
verifyNoMoreInteractions(serverComponent)
|
processManagerProbe.expectNoMessage()
|
||||||
probe.expectMsgPF() { case PingMatcher(_) => () }
|
probe.expectMsgPF() { case PingMatcher(_) => () }
|
||||||
virtualTimeAdvances(testHeartbeatInterval / 2)
|
virtualTimeAdvances(testHeartbeatInterval / 2)
|
||||||
probe.expectNoMessage()
|
probe.expectNoMessage()
|
||||||
@ -108,35 +100,6 @@ class LanguageServerSupervisorSpec
|
|||||||
fakeServer.stop()
|
fakeServer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
it should "restart server limited number of times" in new TestCtx {
|
|
||||||
//given
|
|
||||||
when(serverComponent.restart()).thenReturn(Future.failed(new Exception))
|
|
||||||
val probe = TestProbe()
|
|
||||||
fakeServer.withBehaviour { case ping @ PingMatcher(_) =>
|
|
||||||
probe.ref ! ping
|
|
||||||
Reject
|
|
||||||
}
|
|
||||||
probe.expectNoMessage()
|
|
||||||
//when
|
|
||||||
virtualTimeAdvances(testInitialDelay)
|
|
||||||
probe.expectMsgPF(5.seconds) { case PingMatcher(_) => () }
|
|
||||||
verifyNoInteractions(serverComponent)
|
|
||||||
virtualTimeAdvances(testHeartbeatTimeout)
|
|
||||||
(1 to testRestartLimit).foreach { i =>
|
|
||||||
verify(serverComponent, timeout(VerificationTimeout).times(i)).restart()
|
|
||||||
virtualTimeAdvances(testRestartDelay)
|
|
||||||
}
|
|
||||||
virtualTimeAdvances(testHeartbeatInterval)
|
|
||||||
probe.expectNoMessage()
|
|
||||||
verifyNoMoreInteractions(serverComponent)
|
|
||||||
//then
|
|
||||||
parentProbe.expectMsg(ServerDied)
|
|
||||||
parentProbe.expectMsg(ChildTerminated)
|
|
||||||
//teardown
|
|
||||||
system.stop(parent)
|
|
||||||
fakeServer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override def afterAll(): Unit = {
|
override def afterAll(): Unit = {
|
||||||
TestKit.shutdownActorSystem(system)
|
TestKit.shutdownActorSystem(system)
|
||||||
}
|
}
|
||||||
@ -147,8 +110,6 @@ class LanguageServerSupervisorSpec
|
|||||||
|
|
||||||
val virtualTime = new VirtualTime
|
val virtualTime = new VirtualTime
|
||||||
|
|
||||||
val serverComponent = mock[LifecycleComponent]
|
|
||||||
|
|
||||||
val testHost = "127.0.0.1"
|
val testHost = "127.0.0.1"
|
||||||
|
|
||||||
val testRpcPort = Tcp.findAvailablePort(testHost, 49152, 55535)
|
val testRpcPort = Tcp.findAvailablePort(testHost, 49152, 55535)
|
||||||
@ -168,13 +129,11 @@ class LanguageServerSupervisorSpec
|
|||||||
val fakeServer = new ProgrammableWebSocketServer(testHost, testRpcPort)
|
val fakeServer = new ProgrammableWebSocketServer(testHost, testRpcPort)
|
||||||
fakeServer.start()
|
fakeServer.start()
|
||||||
|
|
||||||
val serverConfig =
|
val connectionInfo =
|
||||||
LanguageServerConfig(
|
LanguageServerConnectionInfo(
|
||||||
testHost,
|
testHost,
|
||||||
testRpcPort,
|
testRpcPort,
|
||||||
testDataPort,
|
testDataPort
|
||||||
UUID.randomUUID(),
|
|
||||||
"/tmp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val supervisionConfig =
|
val supervisionConfig =
|
||||||
@ -188,15 +147,28 @@ class LanguageServerSupervisorSpec
|
|||||||
|
|
||||||
val parentProbe = TestProbe()
|
val parentProbe = TestProbe()
|
||||||
|
|
||||||
|
val processManagerProbe = TestProbe()
|
||||||
|
var restartRequests = 0
|
||||||
|
processManagerProbe
|
||||||
|
.setAutoPilot((sender: ActorRef, msg: Any) =>
|
||||||
|
msg match {
|
||||||
|
case Restart =>
|
||||||
|
restartRequests += 1
|
||||||
|
sender ! ServerBooted(connectionInfo, processManagerProbe.ref)
|
||||||
|
TestActor.KeepRunning
|
||||||
|
case _ => TestActor.KeepRunning
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val parent = system.actorOf(
|
val parent = system.actorOf(
|
||||||
Props(
|
Props(
|
||||||
new StepParent(
|
new StepParent(
|
||||||
LanguageServerSupervisor.props(
|
LanguageServerSupervisor.props(
|
||||||
serverConfig,
|
connectionInfo = connectionInfo,
|
||||||
serverComponent,
|
serverProcessManager = processManagerProbe.ref,
|
||||||
supervisionConfig,
|
supervisionConfig = supervisionConfig,
|
||||||
new AkkaBasedWebSocketConnectionFactory(),
|
connectionFactory = new AkkaBasedWebSocketConnectionFactory(),
|
||||||
virtualTime.scheduler
|
scheduler = virtualTime.scheduler
|
||||||
),
|
),
|
||||||
parentProbe.ref
|
parentProbe.ref
|
||||||
)
|
)
|
||||||
|
@ -64,7 +64,8 @@ class EngineManagementApiSpec extends BaseServerSpec with FlakySpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
client.expectJson(
|
client.expectTaskStarted()
|
||||||
|
client.expectJsonAfterSomeProgress(
|
||||||
json"""
|
json"""
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
@ -84,7 +85,8 @@ class EngineManagementApiSpec extends BaseServerSpec with FlakySpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
client.expectJson(
|
client.expectTaskStarted()
|
||||||
|
client.expectJsonAfterSomeProgress(
|
||||||
json"""
|
json"""
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
@ -164,11 +166,15 @@ class EngineManagementApiSpec extends BaseServerSpec with FlakySpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
val message =
|
||||||
|
"Installation has been cancelled by the user because the requested " +
|
||||||
|
"engine release is marked as broken."
|
||||||
client.expectJson(json"""
|
client.expectJson(json"""
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
"id":0,
|
"id":0,
|
||||||
"error": { "code": 4021, "message": "Installation has been cancelled by the user because the requested engine release is marked as broken." }
|
"error": { "code": 4021, "message": $message }
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@ -182,7 +188,8 @@ class EngineManagementApiSpec extends BaseServerSpec with FlakySpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
client.expectJson(
|
client.expectTaskStarted()
|
||||||
|
client.expectJsonAfterSomeProgress(
|
||||||
json"""
|
json"""
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
package org.enso.projectmanager.protocol
|
||||||
|
|
||||||
|
import io.circe.Json
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.projectmanager.BaseServerSpec
|
||||||
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
|
import org.enso.testkit.RetrySpec
|
||||||
|
import org.scalatest.wordspec.AnyWordSpecLike
|
||||||
|
|
||||||
|
trait MissingComponentBehavior {
|
||||||
|
this: BaseServerSpec with AnyWordSpecLike with RetrySpec =>
|
||||||
|
def buildRequest(
|
||||||
|
version: SemVer,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): Json
|
||||||
|
|
||||||
|
def isSuccess(json: Json): Boolean
|
||||||
|
|
||||||
|
private val defaultVersion = SemVer(0, 0, 1)
|
||||||
|
private val brokenVersion = SemVer(0, 999, 0, Some("broken"))
|
||||||
|
|
||||||
|
def correctlyHandleMissingComponents(): Unit = {
|
||||||
|
"fail if a missing version is requested with Fail" in {
|
||||||
|
val client = new WsTestClient(address)
|
||||||
|
client.send(buildRequest(defaultVersion, MissingComponentAction.Fail))
|
||||||
|
client.expectError(4020)
|
||||||
|
}
|
||||||
|
|
||||||
|
"install the missing version and succeed with Install" taggedAs Retry in {
|
||||||
|
val client = new WsTestClient(address)
|
||||||
|
client.send(
|
||||||
|
buildRequest(defaultVersion, MissingComponentAction.Install)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** We do not check for success here as we are concerned onyl that the
|
||||||
|
* installation is attempted. Installation and creating/opening projects
|
||||||
|
* are tested elsewhere.
|
||||||
|
*/
|
||||||
|
client.expectTaskStarted()
|
||||||
|
}
|
||||||
|
|
||||||
|
"fail if the requested missing version is marked as broken with " +
|
||||||
|
"Install" in {
|
||||||
|
val client = new WsTestClient(address)
|
||||||
|
client.send(buildRequest(brokenVersion, MissingComponentAction.Install))
|
||||||
|
client.expectError(4021)
|
||||||
|
}
|
||||||
|
|
||||||
|
"succeed even if the requested missing version is marked as broken " +
|
||||||
|
"with ForceInstallBroken" taggedAs Retry in {
|
||||||
|
val client = new WsTestClient(address)
|
||||||
|
client.send(
|
||||||
|
buildRequest(brokenVersion, MissingComponentAction.ForceInstallBroken)
|
||||||
|
)
|
||||||
|
client.expectTaskStarted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package org.enso.projectmanager.protocol
|
||||||
|
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.literal.JsonStringContext
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
|
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
||||||
|
import org.enso.testkit.RetrySpec
|
||||||
|
import org.enso.pkg.SemVerJson._
|
||||||
|
|
||||||
|
class ProjectCreateMissingComponentsSpec
|
||||||
|
extends BaseServerSpec
|
||||||
|
with RetrySpec
|
||||||
|
with ProjectManagementOps
|
||||||
|
with MissingComponentBehavior {
|
||||||
|
override def buildRequest(
|
||||||
|
version: SemVer,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): Json =
|
||||||
|
json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"method": "project/create",
|
||||||
|
"id": 1,
|
||||||
|
"params": {
|
||||||
|
"name": "testproj",
|
||||||
|
"missingComponentAction": $missingComponentAction,
|
||||||
|
"version": $version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
override def isSuccess(json: Json): Boolean = {
|
||||||
|
val projectId = for {
|
||||||
|
obj <- json.asObject
|
||||||
|
result <- obj("result").flatMap(_.asObject)
|
||||||
|
id <- result("projectId")
|
||||||
|
} yield id
|
||||||
|
projectId.isDefined
|
||||||
|
}
|
||||||
|
|
||||||
|
"project/create" should {
|
||||||
|
behave like correctlyHandleMissingComponents()
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import java.nio.file.Paths
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import io.circe.literal._
|
import io.circe.literal._
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.enso.projectmanager.test.Net.tryConnect
|
import org.enso.projectmanager.test.Net.tryConnect
|
||||||
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
||||||
@ -22,6 +23,8 @@ class ProjectManagementApiSpec
|
|||||||
gen.reset()
|
gen.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val engineToInstall = Some(SemVer(0, 0, 1))
|
||||||
|
|
||||||
"project/create" must {
|
"project/create" must {
|
||||||
|
|
||||||
"check if project name is not empty" taggedAs Flaky in {
|
"check if project name is not empty" taggedAs Flaky in {
|
||||||
@ -31,7 +34,8 @@ class ProjectManagementApiSpec
|
|||||||
"method": "project/create",
|
"method": "project/create",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"params": {
|
"params": {
|
||||||
"name": ""
|
"name": "",
|
||||||
|
"missingComponentAction": "Install"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
@ -124,26 +128,23 @@ class ProjectManagementApiSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
"create project with specific version" in {
|
"create project with specific version" in {
|
||||||
// TODO [RW] this is just a stub test for parsing, it should be replaced
|
|
||||||
// with actual tests once this functionality is implemented
|
|
||||||
implicit val client = new WsTestClient(address)
|
implicit val client = new WsTestClient(address)
|
||||||
client.send(json"""
|
client.send(json"""
|
||||||
{ "jsonrpc": "2.0",
|
{ "jsonrpc": "2.0",
|
||||||
"method": "project/create",
|
"method": "project/create",
|
||||||
"id": 0,
|
"id": 1,
|
||||||
"params": {
|
"params": {
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"version": "1.2.3"
|
"version": "0.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
client.expectJson(json"""
|
client.expectJson(json"""
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc" : "2.0",
|
||||||
"id":0,
|
"id" : 1,
|
||||||
"error":{
|
"result" : {
|
||||||
"code":10,
|
"projectId" : $getGeneratedUUID
|
||||||
"message":"The requested method is not implemented"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
package org.enso.projectmanager.protocol
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
import akka.testkit.TestActors.blackholeProps
|
||||||
|
import io.circe.Json
|
||||||
|
import io.circe.literal.JsonStringContext
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.pkg.SemVerEnsoVersion
|
||||||
|
import org.enso.projectmanager.data.MissingComponentAction
|
||||||
|
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
||||||
|
import org.enso.testkit.RetrySpec
|
||||||
|
import zio.Runtime
|
||||||
|
|
||||||
|
class ProjectOpenMissingComponentsSpec
|
||||||
|
extends BaseServerSpec
|
||||||
|
with RetrySpec
|
||||||
|
with ProjectManagementOps
|
||||||
|
with MissingComponentBehavior {
|
||||||
|
val ordinaryVersion = SemVer(0, 0, 1)
|
||||||
|
override val engineToInstall: Option[SemVer] = Some(ordinaryVersion)
|
||||||
|
var ordinaryProject: UUID = _
|
||||||
|
var brokenProject: UUID = _
|
||||||
|
|
||||||
|
override val deleteProjectsRootAfterEachTest = false
|
||||||
|
override def beforeAll(): Unit = {
|
||||||
|
super.beforeAll()
|
||||||
|
|
||||||
|
val blackhole = system.actorOf(blackholeProps)
|
||||||
|
val ordinaryAction = projectService.createUserProject(
|
||||||
|
blackhole,
|
||||||
|
"proj1",
|
||||||
|
ordinaryVersion,
|
||||||
|
MissingComponentAction.Fail
|
||||||
|
)
|
||||||
|
ordinaryProject = Runtime.default.unsafeRun(ordinaryAction)
|
||||||
|
val brokenName = "projbroken"
|
||||||
|
val brokenAction = projectService.createUserProject(
|
||||||
|
blackhole,
|
||||||
|
brokenName,
|
||||||
|
ordinaryVersion,
|
||||||
|
MissingComponentAction.Fail
|
||||||
|
)
|
||||||
|
brokenProject = Runtime.default.unsafeRun(brokenAction)
|
||||||
|
|
||||||
|
// TODO [RW] this hack should not be necessary with #1273
|
||||||
|
val projectDir = new File(userProjectDir, brokenName)
|
||||||
|
val pkgManager = org.enso.pkg.PackageManager.Default
|
||||||
|
val pkg = pkgManager.loadPackage(projectDir).get
|
||||||
|
pkg.updateConfig(config =>
|
||||||
|
config.copy(ensoVersion =
|
||||||
|
SemVerEnsoVersion(SemVer(0, 999, 0, Some("broken")))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
uninstallEngine(ordinaryVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def buildRequest(
|
||||||
|
version: SemVer,
|
||||||
|
missingComponentAction: MissingComponentAction
|
||||||
|
): Json = {
|
||||||
|
val projectId =
|
||||||
|
if (version.preRelease.contains("broken")) brokenProject
|
||||||
|
else ordinaryProject
|
||||||
|
|
||||||
|
json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"method": "project/open",
|
||||||
|
"id": 1,
|
||||||
|
"params": {
|
||||||
|
"projectId": $projectId,
|
||||||
|
"missingComponentAction": $missingComponentAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isSuccess(json: Json): Boolean = {
|
||||||
|
val result = for {
|
||||||
|
obj <- json.asObject
|
||||||
|
result <- obj("result").flatMap(_.asObject)
|
||||||
|
} yield result
|
||||||
|
result.isDefined
|
||||||
|
}
|
||||||
|
|
||||||
|
"project/open" should {
|
||||||
|
behave like correctlyHandleMissingComponents()
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,12 @@
|
|||||||
minimum-launcher-version: 0.0.1
|
minimum-launcher-version: 0.0.1
|
||||||
graal-vm-version: 2.0.0
|
graal-vm-version: 2.0.0
|
||||||
graal-java-version: 11
|
graal-java-version: 11
|
||||||
|
jvm-options:
|
||||||
|
- value: "-Dpolyglot.engine.IterativePartialEscape=true"
|
||||||
|
- value: "-Dtruffle.class.path.append=$enginePackagePath\\component\\runtime.jar"
|
||||||
|
os: "windows"
|
||||||
|
- value: "-Dtruffle.class.path.append=$enginePackagePath/component/runtime.jar"
|
||||||
|
os: "linux"
|
||||||
|
- value: "-Dtruffle.class.path.append=$enginePackagePath/component/runtime.jar"
|
||||||
|
os: "macos"
|
||||||
|
- value: "-Denso.version.override=0.0.1"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Hello"
|
echo "Fake JVM executable has been executed"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Hello"
|
echo "Fake JVM executable has been executed"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Hello"
|
echo "Fake JVM executable has been executed"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Hello"
|
echo "Fake JVM executable has been executed"
|
||||||
|
@ -4,7 +4,7 @@ import buildinfo.Info
|
|||||||
import com.typesafe.scalalogging.Logger
|
import com.typesafe.scalalogging.Logger
|
||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
|
||||||
/** Helper object that allows to get the current launcher version.
|
/** Helper object that allows to get the current application version.
|
||||||
*
|
*
|
||||||
* In development-mode it allows to override the returned version for testing
|
* In development-mode it allows to override the returned version for testing
|
||||||
* purposes.
|
* purposes.
|
||||||
|
@ -4,8 +4,13 @@ import java.nio.file.{Files, Path, StandardOpenOption}
|
|||||||
|
|
||||||
import com.typesafe.scalalogging.Logger
|
import com.typesafe.scalalogging.Logger
|
||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.runtimeversionmanager.FileSystem
|
||||||
import org.enso.runtimeversionmanager.FileSystem.PathSyntax
|
import org.enso.runtimeversionmanager.FileSystem.PathSyntax
|
||||||
import org.enso.runtimeversionmanager.archive.Archive
|
import org.enso.runtimeversionmanager.archive.Archive
|
||||||
|
import org.enso.runtimeversionmanager.distribution.{
|
||||||
|
DistributionManager,
|
||||||
|
TemporaryDirectoryManager
|
||||||
|
}
|
||||||
import org.enso.runtimeversionmanager.locking.{
|
import org.enso.runtimeversionmanager.locking.{
|
||||||
LockType,
|
LockType,
|
||||||
Resource,
|
Resource,
|
||||||
@ -14,11 +19,6 @@ import org.enso.runtimeversionmanager.locking.{
|
|||||||
import org.enso.runtimeversionmanager.releases.ReleaseProvider
|
import org.enso.runtimeversionmanager.releases.ReleaseProvider
|
||||||
import org.enso.runtimeversionmanager.releases.engine.EngineRelease
|
import org.enso.runtimeversionmanager.releases.engine.EngineRelease
|
||||||
import org.enso.runtimeversionmanager.releases.graalvm.GraalVMRuntimeReleaseProvider
|
import org.enso.runtimeversionmanager.releases.graalvm.GraalVMRuntimeReleaseProvider
|
||||||
import org.enso.runtimeversionmanager.FileSystem
|
|
||||||
import org.enso.runtimeversionmanager.distribution.{
|
|
||||||
DistributionManager,
|
|
||||||
TemporaryDirectoryManager
|
|
||||||
}
|
|
||||||
|
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
import scala.util.{Failure, Success, Try, Using}
|
import scala.util.{Failure, Success, Try, Using}
|
||||||
|
@ -20,18 +20,7 @@ class ResourceManager(lockManager: LockManager) {
|
|||||||
resource: Resource,
|
resource: Resource,
|
||||||
lockType: LockType
|
lockType: LockType
|
||||||
)(action: => R): R = {
|
)(action: => R): R = {
|
||||||
var waited = false
|
Using(acquireResource(waitingInterface, resource, lockType)) { _ =>
|
||||||
Using {
|
|
||||||
lockManager.acquireLockWithWaitingAction(
|
|
||||||
resource.name,
|
|
||||||
lockType = lockType,
|
|
||||||
() => {
|
|
||||||
waited = true
|
|
||||||
waitingInterface.startWaitingForResource(resource)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} { _ =>
|
|
||||||
if (waited) waitingInterface.finishWaitingForResource(resource)
|
|
||||||
action
|
action
|
||||||
}.get
|
}.get
|
||||||
}
|
}
|
||||||
@ -52,6 +41,27 @@ class ResourceManager(lockManager: LockManager) {
|
|||||||
action
|
action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Acquires a resource, handling possible waiting, and returns its [[Lock]]
|
||||||
|
* instance that can be used to unlock it.
|
||||||
|
*/
|
||||||
|
private def acquireResource(
|
||||||
|
waitingInterface: LockUserInterface,
|
||||||
|
resource: Resource,
|
||||||
|
lockType: LockType
|
||||||
|
): Lock = {
|
||||||
|
var waited = false
|
||||||
|
val lock = lockManager.acquireLockWithWaitingAction(
|
||||||
|
resource.name,
|
||||||
|
lockType = lockType,
|
||||||
|
() => {
|
||||||
|
waited = true
|
||||||
|
waitingInterface.startWaitingForResource(resource)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (waited) waitingInterface.finishWaitingForResource(resource)
|
||||||
|
lock
|
||||||
|
}
|
||||||
|
|
||||||
var mainLock: Option[Lock] = None
|
var mainLock: Option[Lock] = None
|
||||||
|
|
||||||
/** Initializes the [[MainLock]].
|
/** Initializes the [[MainLock]].
|
||||||
|
@ -21,10 +21,7 @@ case class Command(command: Seq[String], extraEnv: Seq[(String, String)]) {
|
|||||||
def run(): Try[Int] =
|
def run(): Try[Int] =
|
||||||
wrapError {
|
wrapError {
|
||||||
logger.debug(s"Executing $toString")
|
logger.debug(s"Executing $toString")
|
||||||
val processBuilder = new java.lang.ProcessBuilder(command: _*)
|
val processBuilder = builder()
|
||||||
for ((key, value) <- extraEnv) {
|
|
||||||
processBuilder.environment().put(key, value)
|
|
||||||
}
|
|
||||||
processBuilder.inheritIO()
|
processBuilder.inheritIO()
|
||||||
val process = processBuilder.start()
|
val process = processBuilder.start()
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
@ -44,6 +41,21 @@ case class Command(command: Seq[String], extraEnv: Seq[(String, String)]) {
|
|||||||
processBuilder.!!
|
processBuilder.!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns a [[ProcessBuilder]] that can be used to start the process.
|
||||||
|
*
|
||||||
|
* This is an advanced feature and it has to be used very carefully - the
|
||||||
|
* builder and the constructed process (as long as it is running) must not
|
||||||
|
* leak outside of the enclosing `withCommand` function to preserve the
|
||||||
|
* guarantees that the environment the process requires still exists.
|
||||||
|
*/
|
||||||
|
def builder(): ProcessBuilder = {
|
||||||
|
val processBuilder = new java.lang.ProcessBuilder(command: _*)
|
||||||
|
for ((key, value) <- extraEnv) {
|
||||||
|
processBuilder.environment().put(key, value)
|
||||||
|
}
|
||||||
|
processBuilder
|
||||||
|
}
|
||||||
|
|
||||||
/** Runs the provided action and wraps any errors into a [[Failure]]
|
/** Runs the provided action and wraps any errors into a [[Failure]]
|
||||||
* containing a [[RunnerError]].
|
* containing a [[RunnerError]].
|
||||||
*/
|
*/
|
||||||
|
@ -2,8 +2,37 @@ package org.enso.runtimeversionmanager.runner
|
|||||||
|
|
||||||
/** Represents settings that are used to launch the runtime JVM.
|
/** Represents settings that are used to launch the runtime JVM.
|
||||||
*
|
*
|
||||||
* @param useSystemJVM if set, the system configured JVM is used instead of
|
* @param javaCommandOverride the command should be used to launch the JVM
|
||||||
* the one managed by the launcher
|
* instead of the default JVM provided with the
|
||||||
|
* release; it can be an absolute path to a java
|
||||||
|
* executable
|
||||||
* @param jvmOptions options that should be added to the launched JVM
|
* @param jvmOptions options that should be added to the launched JVM
|
||||||
*/
|
*/
|
||||||
case class JVMSettings(useSystemJVM: Boolean, jvmOptions: Seq[(String, String)])
|
case class JVMSettings(
|
||||||
|
javaCommandOverride: Option[JavaCommand],
|
||||||
|
jvmOptions: Seq[(String, String)]
|
||||||
|
)
|
||||||
|
|
||||||
|
object JVMSettings {
|
||||||
|
|
||||||
|
/** Creates settings that are used to launch the runtime JVM.
|
||||||
|
*
|
||||||
|
* @param useSystemJVM if set, the system configured JVM is used instead of
|
||||||
|
* the one managed by the launcher
|
||||||
|
* @param jvmOptions options that should be added to the launched JVM
|
||||||
|
*/
|
||||||
|
def apply(
|
||||||
|
useSystemJVM: Boolean,
|
||||||
|
jvmOptions: Seq[(String, String)]
|
||||||
|
): JVMSettings =
|
||||||
|
new JVMSettings(
|
||||||
|
if (useSystemJVM) Some(JavaCommand.systemJavaCommand) else None,
|
||||||
|
jvmOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Creates a default instance of [[JVMSettings]] that just use the default
|
||||||
|
* JVM with no options overrides.
|
||||||
|
*/
|
||||||
|
def default: JVMSettings =
|
||||||
|
JVMSettings(useSystemJVM = false, jvmOptions = Seq())
|
||||||
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.enso.runtimeversionmanager.runner
|
||||||
|
|
||||||
|
import org.enso.runtimeversionmanager.components.GraalRuntime
|
||||||
|
|
||||||
|
/** Represents a way of launching the JVM.
|
||||||
|
*
|
||||||
|
* @param executableName name of the `java` executable to run
|
||||||
|
* @param javaHomeOverride if set, asks to override the JAVA_HOME environment
|
||||||
|
* variable when launching the JVM
|
||||||
|
*/
|
||||||
|
case class JavaCommand(
|
||||||
|
executableName: String,
|
||||||
|
javaHomeOverride: Option[String]
|
||||||
|
)
|
||||||
|
|
||||||
|
object JavaCommand {
|
||||||
|
|
||||||
|
/** The [[JavaCommand]] representing the system-configured JVM.
|
||||||
|
*/
|
||||||
|
def systemJavaCommand: JavaCommand = JavaCommand("java", None)
|
||||||
|
|
||||||
|
/** The [[JavaCommand]] representing a managed [[GraalRuntime]].
|
||||||
|
*/
|
||||||
|
def forRuntime(runtime: GraalRuntime): JavaCommand =
|
||||||
|
JavaCommand(
|
||||||
|
executableName = runtime.javaExecutable.toAbsolutePath.normalize.toString,
|
||||||
|
javaHomeOverride =
|
||||||
|
Some(runtime.javaHome.toAbsolutePath.normalize.toString)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -4,13 +4,13 @@ import nl.gn0s1s.bump.SemVer
|
|||||||
|
|
||||||
/** Represents settings that are used to launch the runner JAR.
|
/** Represents settings that are used to launch the runner JAR.
|
||||||
*
|
*
|
||||||
* @param version Enso engine version to use
|
* @param engineVersion Enso engine version to use
|
||||||
* @param runnerArguments arguments that should be passed to the runner
|
* @param runnerArguments arguments that should be passed to the runner
|
||||||
* @param connectLoggerIfAvailable specifies if the ran component should
|
* @param connectLoggerIfAvailable specifies if the ran component should
|
||||||
* connect to launcher's logging service
|
* connect to launcher's logging service
|
||||||
*/
|
*/
|
||||||
case class RunSettings(
|
case class RunSettings(
|
||||||
version: SemVer,
|
engineVersion: SemVer,
|
||||||
runnerArguments: Seq[String],
|
runnerArguments: Seq[String],
|
||||||
connectLoggerIfAvailable: Boolean
|
connectLoggerIfAvailable: Boolean
|
||||||
)
|
)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user