Only persist file stamps in turbo mode

The use of the persistent file stamp cache between watch runs didn't
seem to cause any issues, but there was some chance for inconsistency
between the file stamp cache and the file system so it makes sense to
put it behind the turbo flag.

After changing the default, the watch/on-change scripted test started
failing. It turns out that the reason is that the file stamp cache
managed by the watch process was not pre-filled by task evaluation. For
this reason, the first time a source file was modified, it was treated
as a creation regardless of whether or not it actually was.

To fix this, I add logic to pre-fill the watch file stamp cache if we
are _not_ persisting the file stamps between runs.

I ran a before and after with the scala build performance benchmark tool
and setting the watchPersistFileStamps key to true reduced the median
run time by about 200ms in the non-turbo case.
This commit is contained in:
Ethan Atkins 2019-07-15 14:15:01 -07:00
parent 5e374a8e7d
commit 6c4e23f77c
5 changed files with 43 additions and 14 deletions

View File

@ -8,7 +8,8 @@
package sbt
package internal
import java.io.{ ByteArrayInputStream, InputStream, File => _ }
import java.io.{ ByteArrayInputStream, IOException, InputStream, File => _ }
import java.nio.file.Path
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger }
import sbt.BasicCommandStrings.{
@ -28,7 +29,7 @@ import sbt.internal.util.complete.{ Parser, Parsers }
import sbt.internal.util.{ AttributeKey, JLine, Util }
import sbt.nio.Keys.{ fileInputs, _ }
import sbt.nio.Watch.{ Creation, Deletion, ShowOptions, Update }
import sbt.nio.file.FileAttributes
import sbt.nio.file.{ FileAttributes, Glob }
import sbt.nio.{ FileStamp, FileStamper, Watch }
import sbt.util.{ Level, _ }
@ -328,12 +329,14 @@ private[sbt] object Continuous extends DeprecatedContinuous {
}
val fileStampCache = new FileStamp.Cache
repo.addObserver(t => fileStampCache.invalidate(t.path))
val persistFileStamps = extracted.get(watchPersistFileStamps)
val cachingRepo: FileTreeRepository[FileAttributes] =
if (persistFileStamps) repo else new FileStampRepository(fileStampCache, repo)
try {
val stateWithRepo = state.put(globalFileTreeRepository, repo)
val stateWithRepo = state.put(globalFileTreeRepository, cachingRepo)
val fullState =
addLegacyWatchSetting(
if (extracted.get(watchPersistFileStamps))
stateWithRepo.put(persistentFileStampCache, fileStampCache)
if (persistFileStamps) stateWithRepo.put(persistentFileStampCache, fileStampCache)
else stateWithRepo
)
setup(fullState, commands) { (s, valid, invalid) =>
@ -1091,4 +1094,17 @@ private[sbt] object Continuous extends DeprecatedContinuous {
}
}
private[sbt] class FileStampRepository(
fileStampCache: FileStamp.Cache,
underlying: FileTreeRepository[FileAttributes]
) extends FileTreeRepository[FileAttributes] {
def putIfAbsent(path: Path, stamper: FileStamper): (Option[FileStamp], Option[FileStamp]) =
fileStampCache.putIfAbsent(path, stamper)
override def list(path: Path): Seq[(Path, FileAttributes)] = underlying.list(path)
override def addObserver(observer: Observer[FileEvent[FileAttributes]]): AutoCloseable =
underlying.addObserver(observer)
override def register(glob: Glob): Either[IOException, Observable[FileEvent[FileAttributes]]] =
underlying.register(glob)
override def close(): Unit = underlying.close()
}
}

View File

@ -253,12 +253,12 @@ private[sbt] object FileStamp {
case e => e.value
}
def putIfAbsent(key: Path, stamper: FileStamper): Unit = {
def putIfAbsent(key: Path, stamper: FileStamper): (Option[FileStamp], Option[FileStamp]) = {
underlying.get(key) match {
case null => updateImpl(key, stamper)
case _ =>
case null => (None, updateImpl(key, stamper))
case Right(s) => (Some(s), None)
case Left(_) => (None, None)
}
()
}
def update(key: Path, stamper: FileStamper): (Option[FileStamp], Option[FileStamp]) = {
underlying.get(key) match {

View File

@ -13,6 +13,7 @@ import java.nio.file.{ Files, Path }
import sbt.Project._
import sbt.internal.Clean.ToSeqPath
import sbt.internal.Continuous.FileStampRepository
import sbt.internal.util.{ AttributeKey, SourcePosition }
import sbt.internal.{ Clean, Continuous, DynamicInput, SettingsGraph }
import sbt.nio.FileStamp.{ fileStampJsonFormatter, pathJsonFormatter, _ }
@ -319,10 +320,22 @@ private[sbt] object Settings {
addTaskDefinition(Keys.inputFileStamps in scopedKey.scope := {
val cache = (unmanagedFileStampCache in scopedKey.scope).value
val stamper = (Keys.inputFileStamper in scopedKey.scope).value
val stampFile: Path => Option[(Path, FileStamp)] =
sbt.Keys.state.value.get(globalFileTreeRepository) match {
case Some(repo: FileStampRepository) =>
(path: Path) =>
repo.putIfAbsent(path, stamper) match {
case (None, Some(s)) =>
cache.put(path, s)
Some(path -> s)
case _ => cache.getOrElseUpdate(path, stamper).map(path -> _)
}
case _ =>
(path: Path) => cache.getOrElseUpdate(path, stamper).map(path -> _)
}
(Keys.allInputPathsAndAttributes in scopedKey.scope).value.flatMap {
case (p, a) if a.isRegularFile && !Files.isHidden(p) =>
cache.getOrElseUpdate(p, stamper).map(p -> _)
case _ => None
case (path, a) if a.isRegularFile && !Files.isHidden(path) => stampFile(path)
case _ => None
}
})
private[this] def outputsAndStamps[T: JsonFormat: ToSeqPath](

View File

@ -576,7 +576,7 @@ object Watch {
sbt.Keys.aggregate in watchTasks :== false,
watchTriggeredMessage :== Watch.defaultOnTriggerMessage,
watchForceTriggerOnAnyChange :== false,
watchPersistFileStamps :== true,
watchPersistFileStamps := (sbt.Keys.turbo in ThisBuild).value,
watchTriggers :== Nil,
)
}

View File

@ -33,4 +33,4 @@ watchOnFileInputEvent := { (_, event: Watch.Event) =>
else Watch.Trigger
}
watchAntiEntropy := 0.millis
watchAntiEntropy := 0.millis