Skip to content

Don't serve stale JAR metadata from cache in classpath implementation#6064

Merged
lrytz merged 1 commit intoscala:2.12.xfrom
retronym:topic/cp-cache-minimal
Sep 11, 2017
Merged

Don't serve stale JAR metadata from cache in classpath implementation#6064
lrytz merged 1 commit intoscala:2.12.xfrom
retronym:topic/cp-cache-minimal

Conversation

@retronym
Copy link
Member

@retronym retronym commented Sep 7, 2017

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

In addition to the enclosed unit test, I manually tested that
the manifestation of this problem reported in sbt/zinc#282
is solved.

% git clone sbt/zinc; cd zinc

% git co 3e9f23ae158240c4077b2d9a50cb2b5eafa8cab7~1

% sbt

> ++2.12.4-bin-a966b7d-SNAPSHOT

> compile

> # edit types.json

> compile
[error] one error found
[error] /Users/jz/code/zinc/internal/zinc-apiinfo/src/main/scala/sbt/internal/inc/ClassToAPI.scala:438: not enough arguments for constructor Projection: (x$1: xsbti.api.Type, x$2: String, x$3: String)xsbti.api.Projection.
[error] Unspecified value parameter x$3.
[error]       case None => new api.Projection(Empty, cls)
[error]                    ^

@scala-jenkins scala-jenkins added this to the 2.12.4 milestone Sep 7, 2017
@retronym retronym force-pushed the topic/cp-cache-minimal branch 2 times, most recently from a966b7d to 7b6a148 Compare September 7, 2017 04:46
}
cache.getOrElseUpdate(zipFile, newClassPathInstance)
private def createUsingCache(zipFile: AbstractFile, settings: Settings): ClassPath = {
cache.getOrCreate(zipFile.file.toPath, () => createForZipFile(zipFile))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed needlessly noisy logging here.

private val cache = collection.mutable.Map.empty[java.nio.file.Path, (Stamp, T)]

def getOrCreate(path: java.nio.file.Path, create: () => T): T = cache.synchronized {
val lastModified = Files.getLastModifiedTime(path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems you can avoid calling getLastModifiedTime here (which calls Files.readAttributesinternally) and use attrs.lastModifiedTime() instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could still be addressed :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit also uses attrs.lastModifiedTime()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my bad, I mis-read.

def getOrCreate(path: java.nio.file.Path, create: () => T): T = cache.synchronized {
val lastModified = Files.getLastModifiedTime(path)
val attrs = Files.readAttributes(path, classOf[BasicFileAttributes])
val stamp = Stamp(lastModified, attrs.fileKey())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems attrs.fileKey is null on windows, at least on the version i checked. I guess that's fine, lastModified should be enough? Should we check still keep the fileKey check for other platforms?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, on other platforms we fall back to just the timestamp check. I could declare it as Option[Object] to make it clearer that we've thought about this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine without an Option, a commend will do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed a comment

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
@retronym retronym force-pushed the topic/cp-cache-minimal branch from 7b6a148 to bcf47b1 Compare September 11, 2017 01:49
@lrytz
Copy link
Member

lrytz commented Sep 11, 2017

Very nice to have this fixed, thanks!

@lrytz lrytz merged commit 4da4ede into scala:2.12.x Sep 11, 2017
@lrytz
Copy link
Member

lrytz commented Sep 11, 2017

At some point in the future we can hopefully remove the -YdisableFlatCpCaching setting.

@retronym
Copy link
Member Author

At some point in the future we can hopefully remove the -YdisableFlatCpCaching setting.

Yep, that would be nice. I can think of two reasons that folks might still want to disable the caching:

  • With caching enabled, a long lived classloader for Global will retain strong references to JAR files, which prevents even File.finalize from closing the file descriptor. This causes problems with inability to overwrite the JAR on windows, and file descriptor leaks for use cases that use lots of different JARs over time.
  • In addition to the file descriptor leak, we also leak the datastructures we are caching.

So we need to have a better story for resource management here, maybe involving reference counting ("are any instance of Global actually using this right now?") and timed eviction ("when was the last time this was used?").

Those things aren't straightforward to retrofit onto a Global and Run that don't currently have a close() method, but are within the purview of scala/scala-dev#416.

allanrenucci added a commit to allanrenucci/dotty that referenced this pull request Sep 13, 2017
allanrenucci added a commit to allanrenucci/dotty that referenced this pull request Sep 13, 2017
allanrenucci added a commit to allanrenucci/dotty that referenced this pull request Sep 13, 2017
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
allanrenucci added a commit to dotty-staging/dotty that referenced this pull request Sep 13, 2017
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
allanrenucci added a commit to dotty-staging/dotty that referenced this pull request Sep 15, 2017
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
@gkossakowski
Copy link
Contributor

We've hit this bug at Stripe while testing 2.12 with bazel. The incremental compilation didn't work for us. Bazel always puts all class files in a jar before passing them to a dependent target (bazel's name for something like "subproject").

Bazel makes a heavy use of symlinks and I'm wondering if cache invalidation implemented in the PR will handle them properly. I'll see whether I can get a nightly distribution build to work with bazel and verify whether 2.12's HEAD fixes this issue for us.

@gkossakowski
Copy link
Contributor

I just verified with the latest Scala nightly: http://www.scala-lang.org/files/archive/nightly/2.12.x/scala-2.12.4-bin-c1c9c7c.tgz

The bug with under compilation in bazel goes away. This is great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants