-
Notifications
You must be signed in to change notification settings - Fork 21
Scalac classpath cache should invalidate modified jars #10295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Fixes sbt#282 Ref scala/bug#10295 exportJars := true exposes JAR file as subproject dependency. Scala 2.12.2 fails to invalidate the source it's used. `-YdisableFlatCpCaching` apparently works aruond this.
+1 Check out sbt/zinc#303 / sbt/zinc#282.
I'd echo @smarter's comment here. Overall the idea of caching makes sense, but it probably needs to check the timestamp or SHA-1 or something to not break existing builds. |
A huge thumbs up to fixing this issue. I want to implement this sbt/zinc#305 in Zinc at some point, and I prefer not to ask users of it to add On an independent topic, is there any particular reason why this caching of jars is performed by Scalac? How big was the performance improvement? |
scala/scala#4060 gives some background, the main usecase is IDEs where each subproject will get its own compiler instance:
I've also observed the dotty test suite running faster after merging the 2.12 classpath implementation in dotty: scala/scala3#2191 |
Thanks for the clarification @smarter. |
@lrytz, what do you think? |
This cache does appear to overreach, I think can we should tame it for 2.12.3. A minimal change would be to flip the default value of the A more nuanced change would be to use file timestamps/size/content-hash, or notifications from the It also represents a memory leak in its current form. |
The following test fails every time in Scala 2.12 with default settings, finding the stale entry of "p1.C" in the second call to import java.nio.file._
object Test {
def main(args: Array[String]): Unit = {
val f = Files.createTempFile("test-", ".jar")
Files.delete(f)
createZip(f, Array(), "p1/C.class")
test(f, "C")
Files.delete(f)
createZip(f, Array(), "p1/D.class")
test(f, "D")
}
def test(p: Path, expectedContets: String): Unit = {
val g = new scala.tools.nsc.Global(new scala.tools.nsc.Settings())
g.settings.usejavacp.value = true
g.settings.classpath.value = p.toString
import g._
new Run
val decls = g.rootMirror.RootPackage.info.decl(TermName("p1")).moduleClass.info.decls
assert(decls.lookup(TermName(expectedContets)) != NoSymbol, decls)
assert(decls.lookup(TypeName(expectedContets)) != NoSymbol, decls)
assert(decls.size == 2)
}
def createZip(zipLocation: Path, content: Array[Byte], internalPath: String): Unit = {
val env = new java.util.HashMap[String, String]()
env.put("create", String.valueOf(Files.notExists(zipLocation)))
val fileUri = zipLocation.toUri
val zipUri = new java.net.URI("jar:" + fileUri.getScheme, fileUri.getPath, null)
val zipfs = FileSystems.newFileSystem(zipUri, env)
try {
try {
val internalTargetPath = zipfs.getPath(internalPath)
Files.createDirectories(internalTargetPath.getParent)
Files.write(internalTargetPath, content)
} finally {
if (zipfs != null) zipfs.close()
}
} finally {
zipfs.close()
}
}
}
However, it also fails about 50% of the time in Scala 2.11, due to the fact that we don't have a #9632 provides a workaround for that problem with In practice, the way that SBT hammers the On my branch, I've added a way to close the |
Note that the implementation of |
Indeed, Java 9 has improved the situation: it considers the last modified time in its caching of JAR listings. Modifying the test above with: g.settings.YdisableFlatCpCaching.value = true And running on 2.12, it passes all the time with Java 9, and fails intermittently with Java 8. |
To summarize and check my understanding:
I haven't read up about |
Java 8 might return an new instance if
I've pushed a commit to my WIP branch that shows how we might implement close correctly in the face of sharing (it needs reference counting). |
/cc @fommil who I think has some relevant knowledge |
LOL, yeah you could say that... I've kind of just given up to be honest, I felt like I was in a well shouting about a leak and nobody cared. I was running a custom version of scala/scala#5592 at some point (via https://github.com/fommil/sbt-scala-monkey) with a third backend that used https://github.com/fommil/class-monkey/ and therefore effectively kept Since we're having a party, let's invite @romanowski and @mpociecha |
looks like a good approach to me! |
My analysis of the internal caching in I'm also preparing a small patch to SBT to call |
I was doing that too, it's not enough... unless you've added lots more close logic. Does that have any performance impact on the classloader reuse? |
I tested with:
// build.sbt
scalaVersion := "2.12.3-bin-e1fc605-SNAPSHOT"
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.0.5" // test.scala
object Test {
scala.xml.Node
{ case class Foo(a: Int, b: Int, d: Int, c: Int) }
{ case class Foo(a: Int, b: Int, d: Int, c: Int) }
// a few thousand more to slow down the compile to make the open/close visible to lsof
}
The |
I'm not proposing any classloader related changes. I expect these changes to be performance neutral. |
Awesome. This would have been hugely valuable to me for the last two years, hopefully it'll help somebody else, and it might even help reduce sbt OOMs. I guess this just closes ArchiveFile handles? N that case, can my change be backed out? There is still the problem of URLClassloader holding JarFile references. I never figured out what was using it, maybe macros? In any case, that's what class-monkey was working around. |
Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Adapted from scalac commit bcf47b165ccfd8e1827188f70aeb24e2cecfb80f by Jason Zaugg: Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Adapted from scalac commit bcf47b165ccfd8e1827188f70aeb24e2cecfb80f by Jason Zaugg: Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Adapted from scalac commit bcf47b165ccfd8e1827188f70aeb24e2cecfb80f by Jason Zaugg in scala/scala#6064: Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Adapted from scalac commit bcf47b165ccfd8e1827188f70aeb24e2cecfb80f by Jason Zaugg in scala/scala#6064: Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Adapted from scalac commit bcf47b165ccfd8e1827188f70aeb24e2cecfb80f by Jason Zaugg in scala/scala#6064: Use the last modified timestamp and the file inode to help detect when the file has been overwritten (as is common in SBT builds with `exportJars := true`, or when using snapshot dependencies). Fixes scala/bug#10295
Since Scala 2.12, classpath entries are cached unless
-YdisableFlatCpCaching
is passed to scalac, this can have very surprising results like breaking this sbt test that usesunmanagedJars
: https://github.com/sbt/sbt/tree/0.13/sbt/src/sbt-test/source-dependencies/binary (you can copy this folder somewhere, setscalacOptions
to2.12.2
and verify that launchingsbt
, doinguse/compile
, removingdep/A.scala
, and doinguse/compile
again compiles even though the dependency does not exist and the jar has been updated, addingscalacOptions += "-YdisableFlatCpCaching"
fixes that.I think the scalac cache in https://github.com/scala/scala/blob/2.12.x/src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala should be more intelligent and invalidate modified jars.
The text was updated successfully, but these errors were encountered: