[2.x] Add a dependency-free weak interner to lm-core

WeakInterner is a ConcurrentHashMap keyed by a WeakReference subclass that
hashes and compares by its referent, with dead entries expunged on each
operation. Ported from zinc's sbt.internal.inc.WeakInterner, so lm-core gains
no interning dependency.

internWith derives the instance to pool only when the value is not pooled yet.
Equality is structural, so a value finds its pooled twin whatever the identity
of the values inside it, which lets a caller whose canonical form is itself
expensive to build skip building it on a hit. intern is internWith with
identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Mai Huy Hoàng 2026-07-29 09:30:52 +07:00
parent 6698589c59
commit 8cea7c1cbc
2 changed files with 210 additions and 0 deletions

View File

@ -0,0 +1,91 @@
/*
* 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.librarymanagement
import java.lang.ref.{ ReferenceQueue, WeakReference }
import java.util.concurrent.ConcurrentHashMap
import scala.annotation.tailrec
/**
* Canonicalizing pool: `intern` returns one instance per distinct value, held weakly.
*
* Ported from zinc's `sbt.internal.inc.WeakInterner` to avoid a new dependency, plus `internWith`.
*/
private[librarymanagement] final class WeakInterner[A <: AnyRef] {
private val stale = new ReferenceQueue[A]
private val pool = new ConcurrentHashMap[WeakValue[A], WeakValue[A]]
def intern(a: A): A = internWith(a)(identity)
/** Like `intern`, but applies `canonicalize` only on a miss, since equality is structural. */
def internWith(a: A)(canonicalize: A => A): A = {
expunge()
lookup(a) match {
case null => publish(canonicalize(a))
case hit => hit
}
}
/** The pooled instance value-equal to `a`, or null if there is none. */
private def lookup(a: A): A = {
val probe = new WeakValue(a, stale)
try
pool.get(probe) match {
case null => null.asInstanceOf[A]
case existing => existing.get // null if it was collected since it matched
}
finally probe.clear() // never enqueue a reference that was not pooled
}
private def publish(a: A): A = {
val candidate = new WeakValue(a, stale)
@tailrec def attempt(): A = pool.putIfAbsent(candidate, candidate) match {
case null => a
case existing =>
existing.get match {
case null => // collected since it matched: drop the dead entry and retry
pool.remove(existing, existing)
attempt()
case canonical =>
candidate.clear()
canonical
}
}
attempt()
}
@tailrec private def expunge(): Unit = stale.poll() match {
case null => ()
case dead =>
pool.remove(dead, dead)
expunge()
}
}
/**
* Weak reference that hashes and compares by its referent's value.
*
* The hash is captured eagerly: it must stay stable after the referent is cleared, or the dead entry
* could never be found and removed.
*/
private final class WeakValue[A <: AnyRef](a: A, stale: ReferenceQueue[A])
extends WeakReference[A](a, stale) {
private val hash: Int = a.hashCode
override def hashCode(): Int = hash
override def equals(other: Any): Boolean = other match {
case that: WeakValue[?] =>
(this `eq` that) || {
val value = get
value != null && value == that.get
}
case _ => false
}
}

View File

@ -0,0 +1,119 @@
/*
* 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.librarymanagement
import java.lang.ref.WeakReference
import java.util.IdentityHashMap
import java.util.concurrent.{ ConcurrentLinkedQueue, CountDownLatch }
object WeakInternerSpec extends verify.BasicTestSuite:
test("intern returns one instance per distinct value"):
val pool = new WeakInterner[Key]
val first = Key("shared")
assert(pool.intern(first) eq first, "the first instance interned is the canonical one")
val second = Key("shared")
assert(first ne second)
assert(pool.intern(second) eq first, "a value-equal input must return the canonical instance")
test("interning preserves value equality"):
val pool = new WeakInterner[Key]
val k = Key("preserved")
assert(pool.intern(k) == k)
test("distinct values get distinct instances"):
val pool = new WeakInterner[Key]
val a = pool.intern(Key("a"))
val b = pool.intern(Key("b"))
assert(a ne b)
assert(a == Key("a") && b == Key("b"))
test("an interned value is released once nothing else references it"):
// Only a weak reference to the canonical instance is kept, so a strong pool would pin it for the
// classloader's life. The pool must let the GC reclaim it.
val pool = new WeakInterner[Key]
val weak = new WeakReference(pool.intern(Key("released")))
assert(awaitCleared(weak), "the pool must not pin a value no caller references")
test("a collected entry does not block re-interning the same value"):
// Exercises the retry path: putIfAbsent matches a dead entry, which has to be dropped and the
// fresh candidate published in its place.
val pool = new WeakInterner[Key]
val weak = new WeakReference(pool.intern(Key("recycled")))
assert(awaitCleared(weak))
val fresh = Key("recycled")
assert(pool.intern(fresh) eq fresh, "a dead entry must be replaced, not returned")
test("internWith derives the pooled instance only when the value is not pooled yet"):
val pool = new WeakInterner[Key]
var derived = 0
val canonicalize: Key => Key = k =>
derived += 1
k
val first = pool.internWith(Key("derived"))(canonicalize)
assert(derived == 1, "the first intern has to derive what it pools")
val second = pool.internWith(Key("derived"))(canonicalize)
assert(second eq first)
assert(derived == 1, "a hit must not derive a value it would only discard")
test("internWith pools what canonicalize returns, not what it was given"):
// Deriving is only worth having because the pooled instance differs from the argument -- for a
// module report, by holding interned children. Probing by value still has to find it.
val pool = new WeakInterner[Key]
val canonical = Key("canonical")
val pooled = pool.internWith(Key("canonical"))(_ => canonical)
assert(pooled eq canonical)
val later = pool.intern(Key("canonical"))
assert(later eq canonical, "what canonicalize returned is the canonical instance from then on")
test("an entry survives the collection of a value that probed for it"):
// A miss probes with one instance and pools another, value-equal one, so the two hash to the same
// bucket. Once the probe is collected, expunging it must not take the live entry with it.
val pool = new WeakInterner[Key]
val pooled = pool.internWith(Key("probed"))(_ => Key("probed"))
// The probe is already unreachable; this waits for a GC to have run, which is what would enqueue
// it. The canary is built outside the assert because the assert macro records -- and so retains --
// every intermediate value it evaluates.
val canary = new WeakReference(Key("collectable"))
assert(awaitCleared(canary))
val again = pool.intern(Key("probed"))
assert(again eq pooled, "expunge must not evict a live entry a dead probe hashes to")
test("concurrent interning of one value converges on a single instance"):
// The parallel per-project update tasks hit these pools at once, so publication has to be atomic.
val pool = new WeakInterner[Key]
val results = new ConcurrentLinkedQueue[Key]
val start = new CountDownLatch(1)
val workers = (1 to 8).map: _ =>
val t = new Thread(() =>
start.await()
var i = 0
while i < 200 do
results.add(pool.intern(Key("contended")))
i += 1
)
t.start()
t
start.countDown()
workers.foreach(_.join())
val distinct = new IdentityHashMap[Key, Boolean]
results.forEach(k => distinct.put(k, true))
assert(distinct.size == 1, s"expected one canonical instance, got ${distinct.size}")
private case class Key(name: String)
// Weak references are cleared by the GC, which is only advisory, so retry a bounded number of times
// rather than relying on a single System.gc().
private def awaitCleared(ref: WeakReference[?]): Boolean =
var i = 0
while i < 50 && ref.get != null do
System.gc()
Thread.sleep(20)
i += 1
ref.get == null