sbt.internal.InMemoryCacheStore.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
The newest version!
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal
import java.io.IOException
import java.lang.Math.toIntExact
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{ Files, Path }
import java.util.concurrent.atomic.AtomicReference
import com.github.benmanes.caffeine.cache.{ Cache, Caffeine, Weigher }
import sbt.io.IO
import sbt.util.{ CacheStore, CacheStoreFactory, DirectoryStoreFactory }
import sjsonnew.{ JsonReader, JsonWriter }
private[sbt] object InMemoryCacheStore {
private[this] class InMemoryCacheStore(maxSize: Long) extends AutoCloseable {
private[this] val weigher: Weigher[Path, (Any, Long, Int)] = { case (_, (_, _, size)) => size }
private[this] val files: Cache[Path, (Any, Long, Int)] = Caffeine
.newBuilder()
.maximumWeight(maxSize)
.weigher(weigher)
.build()
def get[T](path: Path): Option[(T, Long)] = {
files.getIfPresent(path) match {
case null => None
case (value: T @unchecked, lastModified, _) => Some((value, lastModified))
}
}
def put(path: Path, value: Any, lastModified: Long): Unit = {
try {
if (lastModified > 0) {
val attributes = Files.readAttributes(path, classOf[BasicFileAttributes])
files.put(path, (value, lastModified, toIntExact(attributes.size)))
}
} catch {
case _: IOException | _: ArithmeticException => files.invalidate(path)
}
}
def remove(path: Path): Unit = files.invalidate(path)
override def close(): Unit = {
files.invalidateAll()
files.cleanUp()
}
}
private[this] class CacheStoreImpl(path: Path, store: InMemoryCacheStore, cacheStore: CacheStore)
extends CacheStore {
override def delete(): Unit = cacheStore.delete()
override def read[T]()(implicit reader: JsonReader[T]): T = {
val lastModified = IO.getModifiedTimeOrZero(path.toFile)
store.get[T](path) match {
case Some((value, `lastModified`)) => value
case _ => cacheStore.read[T]()
}
}
override def write[T](value: T)(implicit writer: JsonWriter[T]): Unit = {
/*
* This may be inefficient if multiple threads are concurrently modifying the file.
* There is an assumption that there will be little to no concurrency at the file level
* of this cache. If this assumption is invalidated, we may need to do something more
* complicated.
*/
val lastModified = IO.getModifiedTimeOrZero(path.toFile)
store.get[T](path) match {
case Some((v, `lastModified`)) if v == value => // nothing has changed
case _ =>
store.remove(path)
cacheStore.write(value)
val newLastModified = System.currentTimeMillis
IO.setModifiedTimeOrFalse(path.toFile, newLastModified)
store.put(path, value, newLastModified)
}
}
override def close(): Unit = {
store.remove(path)
cacheStore.close()
}
}
private[this] def factory(
store: InMemoryCacheStore,
path: Path
): CacheStoreFactory = {
val delegate = new DirectoryStoreFactory(path.toFile)
new CacheStoreFactory {
override def make(identifier: String): CacheStore =
new CacheStoreImpl(path.resolve(identifier), store, delegate.make(identifier))
override def sub(identifier: String): CacheStoreFactory =
factory(store, path.resolve(identifier))
}
}
private[sbt] trait CacheStoreFactoryFactory extends AutoCloseable {
def apply(path: Path): CacheStoreFactory
}
private[this] class CacheStoreFactoryFactoryImpl(size: Long) extends CacheStoreFactoryFactory {
private[this] val storeRef = new AtomicReference[InMemoryCacheStore]
override def close(): Unit = Option(storeRef.get).foreach(_.close())
def apply(
path: Path,
): CacheStoreFactory = {
val store = storeRef.get match {
case null =>
storeRef.synchronized {
storeRef.get match {
case null =>
val s = new InMemoryCacheStore(size)
storeRef.set(s)
s
case s => s
}
}
case s => s
}
factory(store, path)
}
}
private[this] object DirectoryFactory extends CacheStoreFactoryFactory {
override def apply(
path: Path,
): CacheStoreFactory = new DirectoryStoreFactory(path.toFile)
override def close(): Unit = {}
}
def factory(size: Long): CacheStoreFactoryFactory =
if (size > 0) new CacheStoreFactoryFactoryImpl(size)
else DirectoryFactory
}