mirror of https://github.com/sbt/sbt.git
switch to using sjson-new-murmurhash
The input validation for caching currently relies on having a stack of `scala.math.Equiv`, which is questionable since it can fallback to universal equality. This is likely related to the intermittent caching behavior we are seeing in https://github.com/sbt/sbt/issues/3226
This commit is contained in:
parent
88395e18a4
commit
b0b9dc5e0f
|
|
@ -113,7 +113,7 @@ lazy val utilCache = (project in file("util-cache")).
|
||||||
settings(
|
settings(
|
||||||
commonSettings,
|
commonSettings,
|
||||||
name := "Util Cache",
|
name := "Util Cache",
|
||||||
libraryDependencies ++= Seq(sjsonnewScalaJson.value, scalaReflect.value)
|
libraryDependencies ++= Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value)
|
||||||
).
|
).
|
||||||
configure(addSbtIO)
|
configure(addSbtIO)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ object Dependencies {
|
||||||
|
|
||||||
val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value }
|
val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value }
|
||||||
val sjsonnewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value }
|
val sjsonnewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value }
|
||||||
|
val sjsonnewMurmurhash = Def.setting { "com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value }
|
||||||
|
|
||||||
def log4jVersion = "2.8.1"
|
def log4jVersion = "2.8.1"
|
||||||
val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion
|
val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,18 @@
|
||||||
package sbt.util
|
package sbt.util
|
||||||
|
|
||||||
import java.net.{ URI, URL }
|
|
||||||
|
|
||||||
import sjsonnew.{ BasicJsonProtocol, JsonFormat }
|
import sjsonnew.{ BasicJsonProtocol, JsonFormat }
|
||||||
|
|
||||||
trait BasicCacheImplicits { self: BasicJsonProtocol =>
|
trait BasicCacheImplicits { self: BasicJsonProtocol =>
|
||||||
|
|
||||||
implicit def basicCache[I: JsonFormat: Equiv, O: JsonFormat]: Cache[I, O] =
|
implicit def basicCache[I: JsonFormat, O: JsonFormat]: Cache[I, O] =
|
||||||
new BasicCache[I, O]()
|
new BasicCache[I, O]()
|
||||||
|
|
||||||
def defaultEquiv[T]: Equiv[T] =
|
|
||||||
new Equiv[T] { def equiv(a: T, b: T) = a == b }
|
|
||||||
|
|
||||||
def wrapEquiv[S, T](f: S => T)(implicit eqT: Equiv[T]): Equiv[S] =
|
|
||||||
new Equiv[S] {
|
|
||||||
def equiv(a: S, b: S) =
|
|
||||||
eqT.equiv(f(a), f(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit def optEquiv[T](implicit t: Equiv[T]): Equiv[Option[T]] =
|
|
||||||
new Equiv[Option[T]] {
|
|
||||||
def equiv(a: Option[T], b: Option[T]) =
|
|
||||||
(a, b) match {
|
|
||||||
case (None, None) => true
|
|
||||||
case (Some(va), Some(vb)) => t.equiv(va, vb)
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
implicit def urlEquiv(implicit uriEq: Equiv[URI]): Equiv[URL] = wrapEquiv[URL, URI](_.toURI)(uriEq)
|
|
||||||
implicit def uriEquiv: Equiv[URI] = defaultEquiv
|
|
||||||
implicit def stringSetEquiv: Equiv[Set[String]] = defaultEquiv
|
|
||||||
implicit def stringMapEquiv: Equiv[Map[String, String]] = defaultEquiv
|
|
||||||
|
|
||||||
implicit def arrEquiv[T](implicit t: Equiv[T]): Equiv[Array[T]] =
|
|
||||||
wrapEquiv((x: Array[T]) => x: Seq[T])(seqEquiv[T](t))
|
|
||||||
|
|
||||||
implicit def seqEquiv[T](implicit t: Equiv[T]): Equiv[Seq[T]] =
|
|
||||||
new Equiv[Seq[T]] {
|
|
||||||
def equiv(a: Seq[T], b: Seq[T]) =
|
|
||||||
a.length == b.length &&
|
|
||||||
((a, b).zipped forall t.equiv)
|
|
||||||
}
|
|
||||||
|
|
||||||
def wrapIn[I, J](implicit f: I => J, g: J => I, jCache: SingletonCache[J]): SingletonCache[I] =
|
def wrapIn[I, J](implicit f: I => J, g: J => I, jCache: SingletonCache[J]): SingletonCache[I] =
|
||||||
new SingletonCache[I] {
|
new SingletonCache[I] {
|
||||||
override def read(from: Input): I = g(jCache.read(from))
|
override def read(from: Input): I = g(jCache.read(from))
|
||||||
override def write(to: Output, value: I) = jCache.write(to, f(value))
|
override def write(to: Output, value: I) = jCache.write(to, f(value))
|
||||||
override def equiv: Equiv[I] = wrapEquiv(f)(jCache.equiv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def singleton[T](t: T): SingletonCache[T] =
|
def singleton[T](t: T): SingletonCache[T] =
|
||||||
SingletonCache.basicSingletonCache(asSingleton(t), trueEquiv)
|
SingletonCache.basicSingletonCache(asSingleton(t))
|
||||||
|
|
||||||
def trueEquiv[T] = new Equiv[T] { def equiv(a: T, b: T) = true }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,5 @@ object Cache {
|
||||||
println(label + ".write: " + value)
|
println(label + ".write: " + value)
|
||||||
cache.write(to, value)
|
cache.write(to, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def equiv: Equiv[I] = new Equiv[I] {
|
|
||||||
def equiv(a: I, b: I) = {
|
|
||||||
val equ = cache.equiv.equiv(a, b)
|
|
||||||
println(label + ".equiv(" + a + ", " + b + "): " + equ)
|
|
||||||
equ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,58 +6,54 @@ package sbt.util
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
import sjsonnew.JsonFormat
|
import sjsonnew.JsonFormat
|
||||||
|
import sjsonnew.support.murmurhash.Hasher
|
||||||
|
|
||||||
import CacheImplicits._
|
import CacheImplicits._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache that stores a single value.
|
* A cache that stores a single value.
|
||||||
*/
|
*/
|
||||||
trait SingletonCache[T] {
|
trait SingletonCache[A] {
|
||||||
/** Reads the cache from the backing `from`. */
|
/** Reads the cache from the backing `from`. */
|
||||||
def read(from: Input): T
|
def read(from: Input): A
|
||||||
|
|
||||||
/** Writes `value` to the backing `to`. */
|
/** Writes `value` to the backing `to`. */
|
||||||
def write(to: Output, value: T): Unit
|
def write(to: Output, value: A): Unit
|
||||||
|
|
||||||
/** Equivalence for elements of type `T`. */
|
|
||||||
def equiv: Equiv[T]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object SingletonCache {
|
object SingletonCache {
|
||||||
|
|
||||||
implicit def basicSingletonCache[T: JsonFormat: Equiv]: SingletonCache[T] =
|
implicit def basicSingletonCache[A: JsonFormat]: SingletonCache[A] =
|
||||||
new SingletonCache[T] {
|
new SingletonCache[A] {
|
||||||
override def read(from: Input): T = from.read[T]
|
override def read(from: Input): A = from.read[A]
|
||||||
override def write(to: Output, value: T) = to.write(value)
|
override def write(to: Output, value: A) = to.write(value)
|
||||||
override def equiv: Equiv[T] = implicitly
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A lazy `SingletonCache` */
|
/** A lazy `SingletonCache` */
|
||||||
def lzy[T: JsonFormat: Equiv](mkCache: => SingletonCache[T]): SingletonCache[T] =
|
def lzy[A: JsonFormat](mkCache: => SingletonCache[A]): SingletonCache[A] =
|
||||||
new SingletonCache[T] {
|
new SingletonCache[A] {
|
||||||
lazy val cache = mkCache
|
lazy val cache = mkCache
|
||||||
override def read(from: Input): T = cache.read(from)
|
override def read(from: Input): A = cache.read(from)
|
||||||
override def write(to: Output, value: T) = cache.write(to, value)
|
override def write(to: Output, value: A) = cache.write(to, value)
|
||||||
override def equiv = cache.equiv
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple key-value cache.
|
* Simple key-value cache.
|
||||||
*/
|
*/
|
||||||
class BasicCache[I: JsonFormat: Equiv, O: JsonFormat] extends Cache[I, O] {
|
class BasicCache[I: JsonFormat, O: JsonFormat] extends Cache[I, O] {
|
||||||
private val singletonCache: SingletonCache[(I, O)] = implicitly
|
private val singletonCache: SingletonCache[(Long, O)] = implicitly
|
||||||
val equiv: Equiv[I] = implicitly
|
val jsonFormat: JsonFormat[I] = implicitly
|
||||||
override def apply(store: CacheStore)(key: I): CacheResult[O] =
|
override def apply(store: CacheStore)(key: I): CacheResult[O] = {
|
||||||
|
val keyHash: Long = Hasher.hashUnsafe[I](key).toLong
|
||||||
Try {
|
Try {
|
||||||
val (previousKey, previousValue) = singletonCache.read(store)
|
val (previousKeyHash, previousValue) = singletonCache.read(store)
|
||||||
if (equiv.equiv(key, previousKey))
|
if (keyHash == previousKeyHash) Hit(previousValue)
|
||||||
Hit(previousValue)
|
else Miss(update(store)(keyHash))
|
||||||
else
|
} getOrElse Miss(update(store)(keyHash))
|
||||||
Miss(update(store)(key))
|
}
|
||||||
} getOrElse Miss(update(store)(key))
|
|
||||||
|
|
||||||
private def update(store: CacheStore)(key: I) = (value: O) => {
|
private def update(store: CacheStore)(keyHash: Long) = (value: O) => {
|
||||||
singletonCache.write(store, (key, value))
|
singletonCache.write(store, (keyHash, value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,9 @@ import sbt.io.IO
|
||||||
import sbt.io.syntax._
|
import sbt.io.syntax._
|
||||||
|
|
||||||
import sjsonnew.JsonFormat
|
import sjsonnew.JsonFormat
|
||||||
|
import sjsonnew.support.murmurhash.Hasher
|
||||||
|
|
||||||
object Tracked {
|
object Tracked {
|
||||||
|
|
||||||
import CacheImplicits.LongJsonFormat
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a tracker that provides the last time it was evaluated.
|
* Creates a tracker that provides the last time it was evaluated.
|
||||||
* If the function throws an exception.
|
* If the function throws an exception.
|
||||||
|
|
@ -33,7 +31,10 @@ object Tracked {
|
||||||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||||
* In both cases, the timestamp is not updated if the function throws an exception.
|
* In both cases, the timestamp is not updated if the function throws an exception.
|
||||||
*/
|
*/
|
||||||
def tstamp(store: CacheStore, useStartTime: Boolean): Timestamp = new Timestamp(store, useStartTime)
|
def tstamp(store: CacheStore, useStartTime: Boolean): Timestamp = {
|
||||||
|
import CacheImplicits.LongJsonFormat
|
||||||
|
new Timestamp(store, useStartTime)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a tracker that provides the last time it was evaluated.
|
* Creates a tracker that provides the last time it was evaluated.
|
||||||
|
|
@ -74,7 +75,10 @@ object Tracked {
|
||||||
* recent invocation.
|
* recent invocation.
|
||||||
*/
|
*/
|
||||||
def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(f: (Boolean, I) => O): I => O = { in =>
|
def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(f: (Boolean, I) => O): I => O = { in =>
|
||||||
val cache: SingletonCache[I] = implicitly
|
val cache: SingletonCache[Long] = {
|
||||||
|
import CacheImplicits.LongJsonFormat
|
||||||
|
implicitly
|
||||||
|
}
|
||||||
val help = new CacheHelp(cache)
|
val help = new CacheHelp(cache)
|
||||||
val changed = help.changed(store, in)
|
val changed = help.changed(store, in)
|
||||||
val result = f(changed, in)
|
val result = f(changed, in)
|
||||||
|
|
@ -90,14 +94,19 @@ object Tracked {
|
||||||
def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(f: (Boolean, I) => O): I => O =
|
def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(f: (Boolean, I) => O): I => O =
|
||||||
inputChanged(CacheStore(cacheFile))(f)
|
inputChanged(CacheStore(cacheFile))(f)
|
||||||
|
|
||||||
private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[I]) {
|
private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) {
|
||||||
|
import CacheImplicits.implicitHashWriter
|
||||||
def save(store: CacheStore, value: I): Unit = {
|
def save(store: CacheStore, value: I): Unit = {
|
||||||
store.write(value)
|
store.write(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def changed(store: CacheStore, value: I): Boolean =
|
def changed(store: CacheStore, value: I): Boolean =
|
||||||
Try { store.read[I] } match {
|
Try { store.read[I] } match {
|
||||||
case Success(prev) => !sc.equiv.equiv(value, prev)
|
case Success(prev) =>
|
||||||
|
Hasher.hash(value) match {
|
||||||
|
case Success(keyHash) => keyHash.toLong != prev
|
||||||
|
case Failure(_) => true
|
||||||
|
}
|
||||||
case Failure(_) => true
|
case Failure(_) => true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue