Skip to content

Commit

Permalink
allow eviction of cached Scalafix instances
Browse files Browse the repository at this point in the history
Scalafix instances can be memory-consuming (PrintStream's static 8k
BufferedWriter allocations are wasteful and hard to remove for example), so
this allow eviction in case of memory pressure.

To compensate for the added complexity, the implementation was simplified.
* Values are now always updated (even if they haven't changed). This
  probably does not change much performance-wise, as we are trading throwing
  an exception (costly) against a SoftReference instanciation and the update
  in the low-cardinality ConcurrentHashMap.
* SAM conversion is used now that sbt 0.13 / Scala 2.11 are no longer
  supported.
  • Loading branch information
bjaglin committed Oct 4, 2024
1 parent 7bfaeb2 commit 57e8018
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 35 deletions.
36 changes: 15 additions & 21 deletions src/main/scala/scalafix/internal/sbt/BlockingCache.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,26 @@ package scalafix.internal.sbt

import java.{util => ju}

import scala.util.Try
import java.lang.ref.SoftReference

/** A basic thread-safe cache without any eviction. */
/** A basic thread-safe cache with arbitrary eviction on GC pressure. */
class BlockingCache[K, V] {

private val underlying = new ju.concurrent.ConcurrentHashMap[K, V]

private case class SkipUpdate(prev: V) extends Exception
private val underlying =
new ju.concurrent.ConcurrentHashMap[K, SoftReference[V]]

// Evaluations of `transform` are guaranteed to be sequential for the same key
def compute(key: K, transform: Option[V] => Option[V]): V = {
Try(
underlying.compute(
key,
new ju.function.BiFunction[K, V, V] {
override def apply(key: K, prev: V): V = {
transform(Option(prev)).getOrElse(throw SkipUpdate(prev))
}
}
)
).fold(
{
case SkipUpdate(prev) => prev
case ex => throw ex
},
identity
def compute(key: K, transform: Option[V] => V): V = {
// keep the result in a strong reference to avoid eviction
// when executing wrapped compute()
var result: V = null.asInstanceOf[V]
underlying.compute(
key,
(_, prev: SoftReference[V]) => {
result = transform(Option(prev).flatMap(ref => Option(ref.get)))
new SoftReference(result)
}
)
result
}
}
24 changes: 10 additions & 14 deletions src/main/scala/scalafix/internal/sbt/ScalafixInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ object ScalafixInterface {
None
),
{
case Some(_) =>
case Some(v) =>
// cache hit, don't update
None
v
case None =>
// cache miss, resolve scalafix artifacts and classload them
if (scalafixScalaMajorMinorVersion == "2.11")
Expand All @@ -181,11 +181,9 @@ object ScalafixInterface {
)
)

Some(
(
new ScalafixInterface(scalafixArguments).withArgs(printStream),
Nil
)
(
new ScalafixInterface(scalafixArguments).withArgs(printStream),
Nil
)
}
)
Expand All @@ -201,16 +199,14 @@ object ScalafixInterface {
Some(toolClasspath)
),
{
case Some((_, oldStamps)) if (currentStamps == oldStamps) =>
case Some(v @ (_, oldStamps)) if (currentStamps == oldStamps) =>
// cache hit, don't update
None
v
case _ =>
// cache miss or stale stamps, resolve custom rules artifacts and classload them
Some(
(
buildinRulesInterface.withArgs(toolClasspath),
currentStamps
)
(
buildinRulesInterface.withArgs(toolClasspath),
currentStamps
)
}
)
Expand Down

0 comments on commit 57e8018

Please sign in to comment.