Skip to content

Commit

Permalink
Use another hash code for projects
Browse files Browse the repository at this point in the history
The previous `hashCode` of `Project` was using the hash of the
serialized file. At the time of doing that, I did not realize the use
case that this commit fixes would be affected.

We're now using a unique ID composed of the name and the configuration
path a file comes from. Whenever that is different, it must be a
different project.

We use `ByteHasher` instead of `hashCode` to avoid hash code collisions.
The probability of a hash code collision is very slow, especially
because the server should only have around 100 or 200 projects maximum,
but it's better be safe than sorry.

Fixes #733
  • Loading branch information
jvican committed Nov 23, 2018
1 parent 19b5d6a commit 336d693
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 5 deletions.
5 changes: 4 additions & 1 deletion frontend/src/main/scala/bloop/data/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import bloop.bsp.ProjectUris
import bloop.config.{Config, ConfigEncoderDecoders}
import bloop.engine.Dag
import bloop.engine.tasks.toolchains.{JvmToolchain, ScalaJsToolchain, ScalaNativeToolchain}
import bloop.util.ByteHasher
import ch.epfl.scala.{bsp => Bsp}

final case class Project(
Expand Down Expand Up @@ -58,8 +59,10 @@ final case class Project(
case Config.ScalaThenJava => CompileOrder.ScalaThenJava
}

val uniqueId = s"${origin.path}#${name}"
override def toString: String = s"$name"
override val hashCode: Int = origin.hash
override val hashCode: Int =
ByteHasher.hashBytes(uniqueId.getBytes(StandardCharsets.UTF_8))
override def equals(other: Any): Boolean = {
other match {
case other: Project => this.hashCode == other.hashCode
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/scala/bloop/engine/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ final case class Build private (
}
}

// Recompute all the build -- this step could be incremental but it's negligible
// Recompute all the build -- this step could be incremental but its cost is negligible
Task.gatherUnordered(newOrModifiedConfigurations).map(_.flatten).map { newOrModified =>
val newToAttributed = newFiles.iterator.map(ap => ap.path -> ap).toMap
val deleted = files.toList.collect { case f if !newToAttributed.contains(f.path) => f.path }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ final class ResultsCache private (
def addFinalResults(ps: List[FinalCompileResult]): ResultsCache =
ps.foldLeft(this) { case (rs, FinalCompileResult(b, r, _)) => rs.addResult(b.project, r) }

override def toString: String = s"ResultsCache(${successful.mkString(", ")})"
override def toString: String =
s"""ResultsCache(
| all = ${all.mkString(", ")}
|
| successful = ${successful.mkString(", ")}
|)
""".stripMargin
}

object ResultsCache {
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/test/scala/bloop/nailgun/NailgunSpec.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package bloop.nailgun

import java.nio.file.Files
import java.util.concurrent.TimeUnit

import bloop.io.AbsolutePath
Expand Down Expand Up @@ -84,8 +85,19 @@ class NailgunSpec extends NailgunTestUtils {
)
}

// This test checks that if we exit the nailgun server and compile again, compilation is a no-op
withServerInProject("with-resources") { (logger, client) =>
// Start another nailgun session with the bloop server
val newLogger = new RecordingLogger()
val withResourcesConfigDir = TestUtil.getBloopConfigDir("with-resources")
withServer(withResourcesConfigDir, false, newLogger) { (logger, client) =>
// Force a reload by making a change in the hash of one configuration file
import java.nio.charset.StandardCharsets.UTF_8
val configFile = withResourcesConfigDir.resolve("with-resources.json")
val jsonContents = new String(Files.readAllBytes(configFile), UTF_8)
val newContents =
if (jsonContents.endsWith(" ")) jsonContents.stripSuffix(" ") else jsonContents + " "
Files.write(configFile, newContents.getBytes(UTF_8))

// This test checks that a new nailgun session still produces a no-op compilation
client.success("compile", "with-resources")
val messages = logger.getMessages()
val needle = "Compiling"
Expand Down

0 comments on commit 336d693

Please sign in to comment.