Skip to content

Commit 8c7d94e

Browse files
committed
SI-6412 alleviates leaks in toolboxes, attempt #2
Turns importer caches into fully weak hash maps, and also applies manual cleanup to toolboxes every time they are used. It's not enough, because reflection-mem-typecheck test is still leaking at a rate of ~100kb per typecheck, but it's much better than it was before. We'll fix the rest later, after 2.10.0-final. For more information, see https://issues.scala-lang.org/browse/SI-6412 and http://groups.google.com/group/scala-internals/browse_thread/thread/eabcf3d406dab8b2 In comparison with scala@b403c1d, the original commit that implemented the fix, this one doesn't crash tests. The problem with the original commit was that it called tryFixup() before updating the cache, leading to stack overflows.
1 parent cf08f25 commit 8c7d94e

File tree

5 files changed

+166
-90
lines changed

5 files changed

+166
-90
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,12 +1079,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
10791079
* of what file was being compiled when it broke. Since I really
10801080
* really want to know, this hack.
10811081
*/
1082-
private var lastSeenSourceFile: SourceFile = NoSourceFile
1082+
protected var lastSeenSourceFile: SourceFile = NoSourceFile
10831083

10841084
/** Let's share a lot more about why we crash all over the place.
10851085
* People will be very grateful.
10861086
*/
1087-
private var lastSeenContext: analyzer.Context = null
1087+
protected var lastSeenContext: analyzer.Context = null
10881088

10891089
/** The currently active run
10901090
*/

src/compiler/scala/tools/reflect/ToolBoxFactory.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
4646
newTermName("__wrapper$" + wrapCount + "$" + java.util.UUID.randomUUID.toString.replace("-", ""))
4747
}
4848

49+
// should be called after every use of ToolBoxGlobal in order to prevent leaks
50+
// there's the `withCleanupCaches` method defined below, which provides a convenient interface for that
51+
def cleanupCaches(): Unit = {
52+
perRunCaches.clearAll()
53+
undoLog.clear()
54+
analyzer.lastTreeToTyper = EmptyTree
55+
lastSeenSourceFile = NoSourceFile
56+
lastSeenContext = null
57+
}
58+
59+
def withCleanupCaches[T](body: => T): T =
60+
try body
61+
finally cleanupCaches()
62+
4963
def verify(expr: Tree): Unit = {
5064
// Previously toolboxes used to typecheck their inputs before compiling.
5165
// Actually, the initial demo by Martin first typechecked the reified tree,
@@ -337,7 +351,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
337351
lazy val importer = compiler.mkImporter(u)
338352
lazy val exporter = importer.reverse
339353

340-
def typeCheck(tree: u.Tree, expectedType: u.Type, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): u.Tree = {
354+
def typeCheck(tree: u.Tree, expectedType: u.Type, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): u.Tree = compiler.withCleanupCaches {
341355
if (compiler.settings.verbose.value) println("importing "+tree+", expectedType = "+expectedType)
342356
var ctree: compiler.Tree = importer.importTree(tree)
343357
var cexpectedType: compiler.Type = importer.importType(expectedType)
@@ -357,7 +371,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
357371
inferImplicit(tree, viewTpe, isView = true, silent = silent, withMacrosDisabled = withMacrosDisabled, pos = pos)
358372
}
359373

360-
private def inferImplicit(tree: u.Tree, pt: u.Type, isView: Boolean, silent: Boolean, withMacrosDisabled: Boolean, pos: u.Position): u.Tree = {
374+
private def inferImplicit(tree: u.Tree, pt: u.Type, isView: Boolean, silent: Boolean, withMacrosDisabled: Boolean, pos: u.Position): u.Tree = compiler.withCleanupCaches {
361375
if (compiler.settings.verbose.value) println("importing "+pt, ", tree = "+tree+", pos = "+pos)
362376
var ctree: compiler.Tree = importer.importTree(tree)
363377
var cpt: compiler.Type = importer.importType(pt)

src/reflect/scala/reflect/internal/Importers.scala

Lines changed: 96 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package scala.reflect
22
package internal
3+
34
import scala.collection.mutable.WeakHashMap
5+
import scala.ref.WeakReference
46

57
// SI-6241: move importers to a mirror
68
trait Importers extends api.Importers { self: SymbolTable =>
@@ -26,8 +28,12 @@ trait Importers extends api.Importers { self: SymbolTable =>
2628

2729
val from: SymbolTable
2830

29-
lazy val symMap: WeakHashMap[from.Symbol, Symbol] = new WeakHashMap
30-
lazy val tpeMap: WeakHashMap[from.Type, Type] = new WeakHashMap
31+
protected lazy val symMap = new Cache[from.Symbol, Symbol]()
32+
protected lazy val tpeMap = new Cache[from.Type, Type]()
33+
protected class Cache[K <: AnyRef, V <: AnyRef] extends WeakHashMap[K, WeakReference[V]] {
34+
def weakGet(key: K): Option[V] = this get key flatMap WeakReference.unapply
35+
def weakUpdate(key: K, value: V) = this.update(key, WeakReference(value))
36+
}
3137

3238
// fixups and maps prevent stackoverflows in importer
3339
var pendingSyms = 0
@@ -44,79 +50,81 @@ trait Importers extends api.Importers { self: SymbolTable =>
4450

4551
object reverse extends from.StandardImporter {
4652
val from: self.type = self
47-
for ((fromsym, mysym) <- StandardImporter.this.symMap) symMap += ((mysym, fromsym))
48-
for ((fromtpe, mytpe) <- StandardImporter.this.tpeMap) tpeMap += ((mytpe, fromtpe))
53+
// FIXME this and reverse should be constantly kept in sync
54+
// not just synced once upon the first usage of reverse
55+
for ((fromsym, WeakReference(mysym)) <- StandardImporter.this.symMap) symMap += ((mysym, WeakReference(fromsym)))
56+
for ((fromtpe, WeakReference(mytpe)) <- StandardImporter.this.tpeMap) tpeMap += ((mytpe, WeakReference(fromtpe)))
4957
}
5058

5159
// todo. careful import of positions
5260
def importPosition(pos: from.Position): Position =
5361
pos.asInstanceOf[Position]
5462

5563
def importSymbol(sym0: from.Symbol): Symbol = {
56-
def doImport(sym: from.Symbol): Symbol = {
57-
if (symMap.contains(sym))
58-
return symMap(sym)
59-
60-
val myowner = importSymbol(sym.owner)
61-
val mypos = importPosition(sym.pos)
62-
val myname = importName(sym.name).toTermName
63-
val myflags = sym.flags
64-
def linkReferenced(mysym: TermSymbol, x: from.TermSymbol, op: from.Symbol => Symbol): Symbol = {
65-
symMap(x) = mysym
66-
mysym.referenced = op(x.referenced)
67-
mysym
68-
}
69-
val mysym = sym match {
70-
case x: from.MethodSymbol =>
71-
linkReferenced(myowner.newMethod(myname, mypos, myflags), x, importSymbol)
72-
case x: from.ModuleSymbol =>
73-
linkReferenced(myowner.newModuleSymbol(myname, mypos, myflags), x, importSymbol)
74-
case x: from.FreeTermSymbol =>
75-
newFreeTermSymbol(importName(x.name).toTermName, x.value, x.flags, x.origin) setInfo importType(x.info)
76-
case x: from.FreeTypeSymbol =>
77-
newFreeTypeSymbol(importName(x.name).toTypeName, x.flags, x.origin)
78-
case x: from.TermSymbol =>
79-
linkReferenced(myowner.newValue(myname, mypos, myflags), x, importSymbol)
80-
case x: from.TypeSkolem =>
81-
val origin = x.unpackLocation match {
82-
case null => null
83-
case y: from.Tree => importTree(y)
84-
case y: from.Symbol => importSymbol(y)
64+
def doImport(sym: from.Symbol): Symbol =
65+
symMap weakGet sym match {
66+
case Some(result) => result
67+
case _ =>
68+
val myowner = importSymbol(sym.owner)
69+
val mypos = importPosition(sym.pos)
70+
val myname = importName(sym.name).toTermName
71+
val myflags = sym.flags
72+
def linkReferenced(mysym: TermSymbol, x: from.TermSymbol, op: from.Symbol => Symbol): Symbol = {
73+
symMap.weakUpdate(x, mysym)
74+
mysym.referenced = op(x.referenced)
75+
mysym
8576
}
86-
myowner.newTypeSkolemSymbol(myname.toTypeName, origin, mypos, myflags)
87-
case x: from.ModuleClassSymbol =>
88-
val mysym = myowner.newModuleClass(myname.toTypeName, mypos, myflags)
89-
symMap(x) = mysym
90-
mysym.sourceModule = importSymbol(x.sourceModule)
91-
mysym
92-
case x: from.ClassSymbol =>
93-
val mysym = myowner.newClassSymbol(myname.toTypeName, mypos, myflags)
94-
symMap(x) = mysym
95-
if (sym.thisSym != sym) {
96-
mysym.typeOfThis = importType(sym.typeOfThis)
97-
mysym.thisSym setName importName(sym.thisSym.name)
77+
val mysym = sym match {
78+
case x: from.MethodSymbol =>
79+
linkReferenced(myowner.newMethod(myname, mypos, myflags), x, importSymbol)
80+
case x: from.ModuleSymbol =>
81+
linkReferenced(myowner.newModuleSymbol(myname, mypos, myflags), x, importSymbol)
82+
case x: from.FreeTermSymbol =>
83+
newFreeTermSymbol(importName(x.name).toTermName, x.value, x.flags, x.origin) setInfo importType(x.info)
84+
case x: from.FreeTypeSymbol =>
85+
newFreeTypeSymbol(importName(x.name).toTypeName, x.flags, x.origin)
86+
case x: from.TermSymbol =>
87+
linkReferenced(myowner.newValue(myname, mypos, myflags), x, importSymbol)
88+
case x: from.TypeSkolem =>
89+
val origin = x.unpackLocation match {
90+
case null => null
91+
case y: from.Tree => importTree(y)
92+
case y: from.Symbol => importSymbol(y)
93+
}
94+
myowner.newTypeSkolemSymbol(myname.toTypeName, origin, mypos, myflags)
95+
case x: from.ModuleClassSymbol =>
96+
val mysym = myowner.newModuleClass(myname.toTypeName, mypos, myflags)
97+
symMap.weakUpdate(x, mysym)
98+
mysym.sourceModule = importSymbol(x.sourceModule)
99+
mysym
100+
case x: from.ClassSymbol =>
101+
val mysym = myowner.newClassSymbol(myname.toTypeName, mypos, myflags)
102+
symMap.weakUpdate(x, mysym)
103+
if (sym.thisSym != sym) {
104+
mysym.typeOfThis = importType(sym.typeOfThis)
105+
mysym.thisSym setName importName(sym.thisSym.name)
106+
}
107+
mysym
108+
case x: from.TypeSymbol =>
109+
myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
98110
}
99-
mysym
100-
case x: from.TypeSymbol =>
101-
myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
102-
}
103-
symMap(sym) = mysym
104-
mysym setFlag Flags.LOCKED
105-
mysym setInfo {
106-
val mytypeParams = sym.typeParams map importSymbol
107-
new LazyPolyType(mytypeParams) with FlagAgnosticCompleter {
108-
override def complete(s: Symbol) {
109-
val result = sym.info match {
110-
case from.PolyType(_, res) => res
111-
case result => result
111+
symMap.weakUpdate(sym, mysym)
112+
mysym setFlag Flags.LOCKED
113+
mysym setInfo {
114+
val mytypeParams = sym.typeParams map importSymbol
115+
new LazyPolyType(mytypeParams) with FlagAgnosticCompleter {
116+
override def complete(s: Symbol) {
117+
val result = sym.info match {
118+
case from.PolyType(_, res) => res
119+
case result => result
120+
}
121+
s setInfo GenPolyType(mytypeParams, importType(result))
122+
s setAnnotations (sym.annotations map importAnnotationInfo)
123+
}
112124
}
113-
s setInfo GenPolyType(mytypeParams, importType(result))
114-
s setAnnotations (sym.annotations map importAnnotationInfo)
115125
}
116-
}
117-
}
118-
mysym resetFlag Flags.LOCKED
119-
} // end doImport
126+
mysym resetFlag Flags.LOCKED
127+
} // end doImport
120128

121129
def importOrRelink: Symbol = {
122130
val sym = sym0 // makes sym visible in the debugger
@@ -186,17 +194,18 @@ trait Importers extends api.Importers { self: SymbolTable =>
186194
} // end importOrRelink
187195

188196
val sym = sym0
189-
if (symMap contains sym) {
190-
symMap(sym)
191-
} else {
192-
pendingSyms += 1
193-
194-
try {
195-
symMap getOrElseUpdate (sym, importOrRelink)
196-
} finally {
197-
pendingSyms -= 1
198-
tryFixup()
199-
}
197+
symMap.weakGet(sym) match {
198+
case Some(result) => result
199+
case None =>
200+
pendingSyms += 1
201+
try {
202+
val result = importOrRelink
203+
symMap.weakUpdate(sym, result)
204+
result
205+
} finally {
206+
pendingSyms -= 1
207+
tryFixup()
208+
}
200209
}
201210
}
202211

@@ -258,17 +267,18 @@ trait Importers extends api.Importers { self: SymbolTable =>
258267
def importOrRelink: Type =
259268
doImport(tpe)
260269

261-
if (tpeMap contains tpe) {
262-
tpeMap(tpe)
263-
} else {
264-
pendingTpes += 1
265-
266-
try {
267-
tpeMap getOrElseUpdate (tpe, importOrRelink)
268-
} finally {
269-
pendingTpes -= 1
270-
tryFixup()
271-
}
270+
tpeMap.weakGet(tpe) match {
271+
case Some(result) => result
272+
case None =>
273+
pendingTpes += 1
274+
try {
275+
val result = importOrRelink
276+
tpeMap.weakUpdate(tpe, result)
277+
result
278+
} finally {
279+
pendingTpes -= 1
280+
tryFixup()
281+
}
272282
}
273283
}
274284

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.tools.partest.MemoryTest
2+
3+
trait A { type T <: A }
4+
trait B { type T <: B }
5+
6+
object Test extends MemoryTest {
7+
lazy val tb = {
8+
import scala.reflect.runtime.universe._
9+
import scala.reflect.runtime.{currentMirror => cm}
10+
import scala.tools.reflect.ToolBox
11+
cm.mkToolBox()
12+
}
13+
14+
override def maxDelta = 10
15+
override def calcsPerIter = 8
16+
override def calc() {
17+
var snippet = """
18+
trait A { type T <: A }
19+
trait B { type T <: B }
20+
def foo[T](x: List[T]) = x
21+
foo(List(new A {}, new B {}))
22+
""".trim
23+
snippet = snippet + "\n" + (List.fill(50)(snippet.split("\n").last) mkString "\n")
24+
tb.typeCheck(tb.parse(snippet))
25+
}
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.tools.partest.MemoryTest
2+
3+
trait A { type T <: A }
4+
trait B { type T <: B }
5+
6+
object Test extends MemoryTest {
7+
lazy val tb = {
8+
import scala.reflect.runtime.universe._
9+
import scala.reflect.runtime.{currentMirror => cm}
10+
import scala.tools.reflect.ToolBox
11+
cm.mkToolBox()
12+
}
13+
14+
override def maxDelta = 10
15+
override def calcsPerIter = 3
16+
override def calc() {
17+
var snippet = """
18+
trait A { type T <: A }
19+
trait B { type T <: B }
20+
def foo[T](x: List[T]) = x
21+
foo(List(new A {}, new B {}))
22+
""".trim
23+
snippet = snippet + "\n" + (List.fill(50)(snippet.split("\n").last) mkString "\n")
24+
tb.eval(tb.parse(snippet))
25+
}
26+
}

0 commit comments

Comments
 (0)