Clean up file repository management

I had needed to add proxy classes for the global FileTreeRepository so
that tasks that called the close method wouldn't actually stop the
monitoring done by the global repository. I realized that it makes a lot
more sense to just not provide direct access to the underlying file tree
repository and let the registerGlobalCaches manage its life cycle
instead.
This commit is contained in:
Ethan Atkins 2019-03-29 08:51:57 -07:00
parent 9cdeb7120e
commit 7c2607b1ae
3 changed files with 14 additions and 102 deletions

View File

@ -17,6 +17,7 @@ import sbt.Project.LoadAction
import sbt.compiler.EvalImports
import sbt.internal.Aggregation.AnyKeys
import sbt.internal.CommandStrings.BootCommand
import sbt.internal.FileManagement.CopiedFileTreeRepository
import sbt.internal._
import sbt.internal.inc.ScalaInstance
import sbt.internal.util.Types.{ const, idFun }
@ -847,15 +848,18 @@ object BuiltinCommands {
}
s.put(Keys.stateCompilerCache, cache)
}
private[this] val rawGlobalFileTreeRepository = AttributeKey[FileTreeRepository[FileAttributes]](
"raw-global-file-tree-repository",
"Provides a view into the file system that may or may not cache the tree in memory",
1000
)
private[sbt] def registerGlobalCaches(s: State): State =
try {
val extracted = Project.extract(s)
val cleanedUp = new AtomicBoolean(false)
def cleanup(): Unit = {
s.get(Keys.globalFileTreeRepository).foreach(_.close())
s.attributes.remove(Keys.globalFileTreeRepository)
s.get(rawGlobalFileTreeRepository).foreach(_.close())
s.get(Keys.taskRepository).foreach(_.close())
s.attributes.remove(Keys.taskRepository)
()
}
cleanup()
@ -863,7 +867,8 @@ object BuiltinCommands {
val newState = s.addExitHook(if (cleanedUp.compareAndSet(false, true)) cleanup())
newState
.put(Keys.taskRepository, new TaskRepository.Repr)
.put(Keys.globalFileTreeRepository, fileTreeRepository)
.put(rawGlobalFileTreeRepository, fileTreeRepository)
.put(Keys.globalFileTreeRepository, new CopiedFileTreeRepository(fileTreeRepository))
} catch {
case NonFatal(_) => s
}

View File

@ -20,7 +20,6 @@ import sbt.BasicCommandStrings.{
import sbt.BasicCommands.otherCommandParser
import sbt.Def._
import sbt.Scope.Global
import sbt.internal.FileManagement.FileTreeRepositoryOps
import sbt.internal.LabeledFunctions._
import sbt.internal.io.WatchState
import sbt.internal.util.complete.Parser._
@ -191,7 +190,7 @@ object Continuous extends DeprecatedContinuous {
new IllegalStateException("Tried to access FileTreeRepository for uninitialized state")
state
.get(Keys.globalFileTreeRepository)
.map(FileManagement.toMonitoringRepository(_).copy())
.map(FileManagement.toMonitoringRepository)
.getOrElse(throw exception)
}

View File

@ -13,9 +13,7 @@ import java.util.concurrent.ConcurrentHashMap
import sbt.BasicCommandStrings.ContinuousExecutePrefix
import sbt.internal.io.HybridPollingFileTreeRepository
import sbt.internal.util.Util
import sbt.io.FileTreeDataView.{ Entry, Observable, Observer, Observers }
import sbt.io.Glob.TraversableGlobOps
import sbt.io.{ FileTreeRepository, _ }
import sbt.util.{ Level, Logger }
@ -55,11 +53,8 @@ private[sbt] object FileManagement {
watchLogger
)
} else {
if (Util.isWindows) new PollingFileRepository(FileAttributes.default)
else {
val service = Watched.createWatchService(pollInterval)
FileTreeRepository.legacy(FileAttributes.default _, (_: Any) => {}, service)
}
val service = Watched.createWatchService(pollInterval)
FileTreeRepository.legacy(FileAttributes.default _, (_: Any) => {}, service)
}
}
@ -100,55 +95,6 @@ private[sbt] object FileManagement {
override def close(): Unit = monitor.close()
}
}
private[sbt] implicit class FileTreeRepositoryOps[T](val repo: FileTreeRepository[T])
extends AnyVal {
def copy(): FileTreeRepository[T] =
copy(ConcurrentHashMap.newKeySet[Glob].asScala, closeUnderlying = false)
/**
* Creates a copied FileTreeRepository that keeps track of all of the globs that are explicitly
* registered with it.
*
* @param registered the registered globs
* @param closeUnderlying toggles whether or not close should actually close the delegate
* repository
*
* @return the copied FileTreeRepository
*/
def copy(registered: mutable.Set[Glob], closeUnderlying: Boolean): FileTreeRepository[T] =
new FileTreeRepository[T] {
private val entryFilter: FileTreeDataView.Entry[T] => Boolean =
(entry: FileTreeDataView.Entry[T]) => registered.toEntryFilter(entry)
private[this] val observers = new Observers[T] {
override def onCreate(newEntry: FileTreeDataView.Entry[T]): Unit =
if (entryFilter(newEntry)) super.onCreate(newEntry)
override def onDelete(oldEntry: FileTreeDataView.Entry[T]): Unit =
if (entryFilter(oldEntry)) super.onDelete(oldEntry)
override def onUpdate(
oldEntry: FileTreeDataView.Entry[T],
newEntry: FileTreeDataView.Entry[T]
): Unit = if (entryFilter(newEntry)) super.onUpdate(oldEntry, newEntry)
}
private[this] val handle = repo.addObserver(observers)
override def register(glob: Glob): Either[IOException, Boolean] = {
registered.add(glob)
repo.register(glob)
}
override def unregister(glob: Glob): Unit = repo.unregister(glob)
override def addObserver(observer: FileTreeDataView.Observer[T]): Int =
observers.addObserver(observer)
override def removeObserver(handle: Int): Unit = observers.removeObserver(handle)
override def close(): Unit = {
repo.removeObserver(handle)
if (closeUnderlying) repo.close()
}
override def toString: String = s"CopiedFileTreeRepository(base = $repo)"
override def list(glob: Glob): Seq[TypedPath] = repo.list(glob)
override def listEntries(glob: Glob): Seq[FileTreeDataView.Entry[T]] =
repo.listEntries(glob)
}
}
private[sbt] class HybridMonitoringRepository[T](
underlying: HybridPollingFileTreeRepository[T],
delay: FiniteDuration,
@ -174,11 +120,10 @@ private[sbt] object FileManagement {
private[sbt] def toMonitoringRepository[T](
repository: FileTreeRepository[T]
): FileTreeRepository[T] = repository match {
case p: PollingFileRepository[T] => p.toMonitoringRepository
case h: HybridMonitoringRepository[T] => h.toMonitoringRepository
case r: FileTreeRepository[T] => new CopiedFileRepository(r)
case r: FileTreeRepository[T] => r
}
private class CopiedFileRepository[T](underlying: FileTreeRepository[T])
private[sbt] class CopiedFileTreeRepository[T](underlying: FileTreeRepository[T])
extends FileTreeRepository[T] {
def addObserver(observer: Observer[T]) = underlying.addObserver(observer)
def close(): Unit = {} // Don't close the underlying observable
@ -188,41 +133,4 @@ private[sbt] object FileManagement {
def register(glob: Glob): Either[IOException, Boolean] = underlying.register(glob)
def unregister(glob: Glob): Unit = underlying.unregister(glob)
}
private[sbt] class PollingFileRepository[T](converter: TypedPath => T)
extends FileTreeRepository[T] { self =>
private val registered: mutable.Set[Glob] = ConcurrentHashMap.newKeySet[Glob].asScala
private[this] val view = FileTreeView.DEFAULT
private[this] val dataView = view.asDataView(converter)
private[this] val handles: mutable.Map[FileTreeRepository[T], Int] =
new ConcurrentHashMap[FileTreeRepository[T], Int].asScala
private val observers: Observers[T] = new Observers
override def addObserver(observer: Observer[T]): Int = observers.addObserver(observer)
override def close(): Unit = {
handles.foreach { case (repo, handle) => repo.removeObserver(handle) }
observers.close()
}
override def list(glob: Glob): Seq[TypedPath] = view.list(glob)
override def listEntries(glob: Glob): Seq[Entry[T]] = dataView.listEntries(glob)
override def removeObserver(handle: Int): Unit = observers.removeObserver(handle)
override def register(glob: Glob): Either[IOException, Boolean] = Right(registered.add(glob))
override def unregister(glob: Glob): Unit = registered -= glob
private[sbt] def toMonitoringRepository: FileTreeRepository[T] = {
val legacy = FileTreeRepository.legacy(converter)
registered.foreach(legacy.register)
handles += legacy -> legacy.addObserver(observers)
new FileTreeRepository[T] {
override def listEntries(glob: Glob): Seq[Entry[T]] = legacy.listEntries(glob)
override def list(glob: Glob): Seq[TypedPath] = legacy.list(glob)
def addObserver(observer: Observer[T]): Int = legacy.addObserver(observer)
override def removeObserver(handle: Int): Unit = legacy.removeObserver(handle)
override def close(): Unit = legacy.close()
override def register(glob: Glob): Either[IOException, Boolean] = {
self.register(glob)
legacy.register(glob)
}
override def unregister(glob: Glob): Unit = legacy.unregister(glob)
}
}
}
}