Skip to content

Commit

Permalink
Eliminate VCS TimeoutExceptions on startup (#8080)
Browse files Browse the repository at this point in the history
Having a modest-size files in a project would lead to a timeout when the project was first initialized. This became apparent when testing delivered `.enso-project` files with some data files. After some digging there was a bug in JGit
(https://bugs.eclipse.org/bugs/show_bug.cgi?id=494323) which meant that adding such files was really slow. The implemented fix is not on by default but even with `--renormalization` turned off I did not see improvement.
In the end it didn't make sense to add `data` directory to our version control, or any other files than those in `src` or some meta files in `.enso`. Not including such files eliminates first-use initialization problems.

# Important Notes
To test, pick an existing Enso project with some data files in it (> 100MB) and remove `.enso/.vcs` directory. Previously it would timeout on first try (and work in successive runs). Now it works even on the first try.

The crash:
```
[org.enso.languageserver.requesthandler.vcs.InitVcsHandler] Initialize project request [Number(2)] for [f9a7cd0d-529c-4e1d-a4fa-9dfe2ed79008] failed with: null.
java.util.concurrent.TimeoutException: null
at org.enso.languageserver.effect.ZioExec$.<clinit>(Exec.scala:134)
at org.enso.languageserver.effect.ZioExec.$anonfun$exec$3(Exec.scala:60)
at org.enso.languageserver.effect.ZioExec.$anonfun$exec$3$adapted(Exec.scala:60)
at zio.ZIO.$anonfun$foldCause$4(ZIO.scala:683)
at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:904)
at zio.internal.FiberRuntime.evaluateEffect(FiberRuntime.scala:381)
at zio.internal.FiberRuntime.evaluateMessageWhileSuspended(FiberRuntime.scala:504)
at zio.internal.FiberRuntime.drainQueueOnCurrentThread(FiberRuntime.scala:220)
at zio.internal.FiberRuntime.run(FiberRuntime.scala:139)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:49)
at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
```
  • Loading branch information
hubertp authored Oct 18, 2023
1 parent b039f92 commit 0b58a36
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 182 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
* text eol=lf
*.enso text eol=lf
*.png binary
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ val directoryWatcherVersion = "0.9.10"
val flatbuffersVersion = "1.12.0"
val guavaVersion = "32.0.0-jre"
val jlineVersion = "3.23.0"
val jgitVersion = "6.3.0.202209071007-r"
val jgitVersion = "6.7.0.202309050840-r"
val diffsonVersion = "4.4.0"
val kindProjectorVersion = "0.13.2"
val mockitoScalaVersion = "1.17.14"
Expand Down
9 changes: 7 additions & 2 deletions distribution/engine/THIRD-PARTY/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Copyright notices related to this dependency can be found in the directory `com.

'JavaEWAH', licensed under the Apache 2, is distributed with the engine.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.googlecode.javaewah.JavaEWAH-1.1.13`.
Copyright notices related to this dependency can be found in the directory `com.googlecode.javaewah.JavaEWAH-1.2.3`.


'icu4j', licensed under the Unicode/ICU License, is distributed with the engine.
Expand Down Expand Up @@ -176,6 +176,11 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `commons-cli.commons-cli-1.5.0`.


'commons-codec', licensed under the Apache-2.0, is distributed with the engine.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `commons-codec.commons-codec-1.16.0`.


'commons-io', licensed under the Apache-2.0, 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 `commons-io.commons-io-2.12.0`.
Expand Down Expand Up @@ -303,7 +308,7 @@ Copyright notices related to this dependency can be found in the directory `org.

'org.eclipse.jgit', licensed under the Eclipse Distribution License (New BSD License), is distributed with the engine.
The license file can be found at `licenses/Eclipse_Distribution_License_(New_BSD_License)`.
Copyright notices related to this dependency can be found in the directory `org.eclipse.jgit.org.eclipse.jgit-6.3.0.202209071007-r`.
Copyright notices related to this dependency can be found in the directory `org.eclipse.jgit.org.eclipse.jgit-6.7.0.202309050840-r`.


'jline', licensed under the The BSD License, is distributed with the engine.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -1,45 +1,28 @@
/*
* Copyright 2017 Marc Stevens <[email protected]>, Dan Shumow <[email protected]>
* Distributed under the MIT Software License.
* MIT License
*
* Copyright (c) 2017:
* Marc Stevens
* Cryptology Group
* Centrum Wiskunde & Informatica
* P.O. Box 94079, 1090 GB Amsterdam, Netherlands
* [email protected]
*
* Dan Shumow
* Microsoft Research
* [email protected]
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

<p><b>SHA-1 UbcCheck - MIT</b></p>
<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>

<p>Copyright (c) 2017:</p>
<div class="ubc-name">
Marc Stevens

<p>Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. </p>
<p>Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
<ul><li>Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. </li>
<li>Redistributions in binary form must reproduce the above copyright notice,

<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

<ul><li>Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer. </li>
<li>Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. </li>

<ul><li>The above copyright notice and this permission notice shall be included

ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR

ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

Copyright (C) 2006, 2007, Robin Rosenberg <[email protected]>

Expand Down Expand Up @@ -688,3 +671,23 @@ Copyright (c) 2020, Google LLC and others
Copyright (c) 2020, Google LLC and others

Copyright (c) 2021 Qualcomm Innovation Center, Inc.

Copyright 2017 Marc Stevens <[email protected]>, Dan Shumow <[email protected]>

IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,

Redistributions in binary form must reproduce the above copyright notice,

Redistributions of source code must retain the above copyright

Redistributions of source code must retain the above copyright notice, this

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

and other copyright owners as documented in the project's IP log.

copyright notice, this list of conditions and the following

other copyright owners as documented in the project's IP log.
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import org.enso.languageserver.vcsmanager.Git.{
}

import scala.jdk.CollectionConverters._

import java.time.Instant
import zio.ZIO.attemptBlocking

private class Git(ensoDataDirectory: Option[Path], asyncInit: Boolean)
extends VcsApi[BlockingIO] {
private class Git(
srcDirectory: Path,
ensoDataDirectory: Option[Path],
asyncInit: Boolean
) extends VcsApi[BlockingIO] {

private val gitDir = ensoDataDirectory
.map(_.resolve(VcsApi.DefaultRepoDir))
Expand Down Expand Up @@ -92,18 +94,24 @@ private class Git(ensoDataDirectory: Option[Path], asyncInit: Boolean)
dotGit.delete()
}
}

val isDataDir =
ensoDataDirectory.contains _
val isDataDir = ensoDataDirectory.contains _
val filesToAdd =
listDirectoryFiles(root, Set(gitDir))
.filterNot(isDataDir)
.filter(p =>
ensoDataDirectory
.map(ensoDir => p.startsWith(ensoDir))
.getOrElse(p.startsWith(srcDirectory)) || p.startsWith(
srcDirectory
)
)
.map(ensureUnixPathSeparator)
if (filesToAdd.nonEmpty) {
filesToAdd
.foldLeft(jgit.add()) { case (cmd, filePath) =>
cmd.addFilepattern(filePath)
}
.setRenormalize(false)
.call()
}

Expand Down Expand Up @@ -140,6 +148,7 @@ private class Git(ensoDataDirectory: Option[Path], asyncInit: Boolean)
// Include files that already are/were in the index
jgit
.add()
.setRenormalize(false)
.addFilepattern(".")
.setUpdate(true)
.call()
Expand All @@ -151,12 +160,16 @@ private class Git(ensoDataDirectory: Option[Path], asyncInit: Boolean)
val isDataDir =
(x: String) => unixDataDir.map(dataDir => dataDir == x).getOrElse(false)
val filesToAdd = untracked.flatMap { file =>
if (!file.startsWith(unixGitDir) && !isDataDir(file)) {
if (
!file.startsWith(unixGitDir) && !isDataDir(file) && Path
.of(file)
.startsWith(srcDirectory)
) {
Some(file)
} else None
}
if (!filesToAdd.isEmpty) {
val addCmd = jgit.add()
val addCmd = jgit.add().setRenormalize(false)
filesToAdd.foreach(filePath =>
addCmd.addFilepattern(ensureUnixPathSeparator(filePath))
)
Expand Down Expand Up @@ -345,9 +358,10 @@ private class Git(ensoDataDirectory: Option[Path], asyncInit: Boolean)
}

object Git {
private val HeadRef = "HEAD"
private val AuthorName = "Enso VCS"
private val AuthorEmail = "[email protected]"
private val HeadRef = "HEAD"
private val AuthorName = "Enso VCS"
private val AuthorEmail = "[email protected]"
private val SrcDirectory = Path.of("src")

private class RepoExists extends Exception

Expand All @@ -359,6 +373,6 @@ object Git {
asyncInit: Boolean
): VcsApi[BlockingIO] = {
SystemReader.setInstance(new EmptyUserConfigReader)
new Git(dataDir, asyncInit)
new Git(SrcDirectory, dataDir, asyncInit)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ class GitSpec
"dummy package json file".getBytes(StandardCharsets.UTF_8)
)

val relativeUserDataDirectory = Path.of("data")
val userDataDirectory = repoPath.resolve(relativeUserDataDirectory)
userDataDirectory.toFile.mkdir()
val relativeSomeUserDataFile =
relativeUserDataDirectory.resolve("test.csv")
val absoluteSomeUserDataFile = repoPath.resolve(relativeSomeUserDataFile)
createStubFile(absoluteSomeUserDataFile) should equal(true)
Files.write(
absoluteSomeUserDataFile,
"dummy,data,file".getBytes(StandardCharsets.UTF_8)
)

val relativeRandomDirectory = Path.of("meh")
val userRandomDirectory = repoPath.resolve(relativeRandomDirectory)
userRandomDirectory.toFile.mkdir()
val relativeRandomFile = relativeRandomDirectory.resolve("nothing.csv")
val absoluteRandomFile = repoPath.resolve(relativeRandomFile)
createStubFile(absoluteRandomFile) should equal(true)
Files.write(
absoluteRandomFile,
"dummy,data,file".getBytes(StandardCharsets.UTF_8)
)

val targetRepo =
repoPath.resolve(dataDirectory).resolve(VcsApi.DefaultRepoDir)
targetRepo.toFile shouldNot exist
Expand All @@ -97,17 +120,35 @@ class GitSpec
repoPath.resolve(dataDirectory),
relativePackageJsonFile
) shouldBe true
isPathUnderVcs(
repoPath.resolve(dataDirectory),
relativeRandomFile
) shouldBe false
isPathUnderVcs(
repoPath.resolve(dataDirectory),
relativeSomeUserDataFile
) shouldBe false
}
}

"VCS save" should {
"commit to the repo" in new TestCtx with InitialRepoSetup {
createStubFile(repoPath.resolve("Foo.enso")) should equal(true)
val (srcDirRelative, _) = srcDirPaths()
val bazFile = srcDirRelative.resolve("Baz.enso")
createStubFile(repoPath.resolve(bazFile)) should equal(true)

val commitResult1 = vcs.commit(repoPath, "First").unsafeRunSync()
commitResult1.isRight shouldBe true

createStubFile(repoPath.resolve("Bar.enso")) should equal(true)
val barFile = srcDirRelative.resolve("Bar.enso")
createStubFile(repoPath.resolve(barFile)) should equal(true)
val relativeDataDirectory = Path.of("data")
val dataDirectory = repoPath.resolve(relativeDataDirectory)
dataDirectory.toFile.mkdir()
val relativeUserDataFile = relativeDataDirectory.resolve("test.csv")
val userDataFile = repoPath.resolve(relativeUserDataFile)
createStubFile(userDataFile) should equal(true)
val commitResult2 = vcs.commit(repoPath, "Second").unsafeRunSync()
commitResult2.isRight shouldBe true

Expand All @@ -116,6 +157,18 @@ class GitSpec
revisions(0).getFullMessage() should equal("Second")
revisions(1).getFullMessage() should equal("First")
revisions(2).getFullMessage() should equal("Initial commit")
isPathUnderVcs(
repoPath,
Path.of("Foo.enso")
) shouldBe false
isPathUnderVcs(
repoPath,
bazFile
) shouldBe true
isPathUnderVcs(
repoPath,
relativeUserDataFile
) shouldBe false
}

"accept empty commits to the repo" in new TestCtx with InitialRepoSetup {
Expand Down Expand Up @@ -182,9 +235,10 @@ class GitSpec
repoStatusIgnoreSha(r) should equal(
RepoStatus(false, Set(), Some(RepoCommit(null, "Initial commit")))
)
val (_, srcDir) = srcDirPaths()

createStubFile(repoPath.resolve("Foo.enso")) should equal(true)
createStubFile(repoPath.resolve("Bar.enso")) should equal(true)
createStubFile(srcDir.resolve("Foo.enso")) should equal(true)
createStubFile(srcDir.resolve("Bar.enso")) should equal(true)
val commitResult = vcs.commit(repoPath, "New files").unsafeRunSync()
commitResult.isRight shouldBe true

Expand Down Expand Up @@ -227,9 +281,10 @@ class GitSpec
"VCS restore" should {

"reset to last saved state" in new TestCtx with InitialRepoSetup {
val fooFile = repoPath.resolve("Foo.enso")
val barFile = repoPath.resolve("Bar.enso")
val bazFile = repoPath.resolve("Baz.enso")
val (relativeSrcDir, srcDir) = srcDirPaths()
val fooFile = srcDir.resolve("Foo.enso")
val barFile = srcDir.resolve("Bar.enso")
val bazFile = srcDir.resolve("Baz.enso")
createStubFile(fooFile) should equal(true)
Files.write(
fooFile,
Expand Down Expand Up @@ -260,8 +315,8 @@ class GitSpec
val restoreResult = vcs.restore(repoPath, commitId = None).unsafeRunSync()
restoreResult.isRight shouldBe true
restoreResult.getOrElse(Nil) shouldEqual List(
Path.of("Bar.enso"),
Path.of("Foo.enso")
relativeSrcDir.resolve("Bar.enso"),
relativeSrcDir.resolve("Foo.enso")
)

val text2 = Files.readAllLines(fooFile)
Expand All @@ -273,7 +328,8 @@ class GitSpec

"reset to a named saved state while preserving original line endings" in new TestCtx
with InitialRepoSetup {
val fooFile = repoPath.resolve("Foo.enso")
val (relativeSrcDir, srcDir) = srcDirPaths()
val fooFile = srcDir.resolve("Foo.enso")
createStubFile(fooFile) should equal(true)
val text1 = "file contents\r\nand more\u0000"
Files.write(
Expand All @@ -299,7 +355,9 @@ class GitSpec
val restoreResult =
vcs.restore(repoPath, Some(commitId)).unsafeRunSync()
restoreResult.isRight shouldBe true
restoreResult.getOrElse(Nil) shouldEqual List(Path.of("Foo.enso"))
restoreResult.getOrElse(Nil) shouldEqual List(
relativeSrcDir.resolve("Foo.enso")
)

val fileText2 = Files.readString(fooFile)
fileText2 should equal(text1)
Expand Down Expand Up @@ -424,6 +482,13 @@ class GitSpec
def repoStatusIgnoreSha(r: RepoStatus) = {
r.copy(lastCommit = r.lastCommit.map(_.copy(commitId = null)))
}

def srcDirPaths(): (Path, Path) = {
val relativeSrcDIr = Path.of("src")
val srcDir = repoPath.resolve(relativeSrcDIr)
srcDir.toFile.mkdir()
(relativeSrcDIr, srcDir)
}
}

trait InitialRepoSetup { self: TestCtx =>
Expand Down
Loading

0 comments on commit 0b58a36

Please sign in to comment.