Skip to content

Commit b403c1d

Browse files
committed
SI-6412 alleviates leaks in toolboxes
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
1 parent 291d1f0 commit b403c1d

File tree

5 files changed

+159
-82
lines changed

5 files changed

+159
-82
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: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,23 @@ 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+
lub(List(IntTpe, IntTpe)) // indirectly clears lubResults and glbResults
53+
// I didn't want to turn those caches from private to protected
54+
// in order not to screw up the performance
55+
perRunCaches.clearAll()
56+
undoLog.clear()
57+
analyzer.lastTreeToTyper = EmptyTree
58+
lastSeenSourceFile = NoSourceFile
59+
lastSeenContext = null
60+
}
61+
62+
def withCleanupCaches[T](body: => T): T =
63+
try body
64+
finally cleanupCaches()
65+
4966
def verify(expr: Tree): Unit = {
5067
// Previously toolboxes used to typecheck their inputs before compiling.
5168
// Actually, the initial demo by Martin first typechecked the reified tree,
@@ -337,7 +354,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
337354
lazy val importer = compiler.mkImporter(u)
338355
lazy val exporter = importer.reverse
339356

340-
def typeCheck(tree: u.Tree, expectedType: u.Type, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): u.Tree = {
357+
def typeCheck(tree: u.Tree, expectedType: u.Type, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): u.Tree = compiler.withCleanupCaches {
341358
if (compiler.settings.verbose.value) println("importing "+tree+", expectedType = "+expectedType)
342359
var ctree: compiler.Tree = importer.importTree(tree)
343360
var cexpectedType: compiler.Type = importer.importType(expectedType)
@@ -357,7 +374,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
357374
inferImplicit(tree, viewTpe, isView = true, silent = silent, withMacrosDisabled = withMacrosDisabled, pos = pos)
358375
}
359376

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

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

Lines changed: 86 additions & 78 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,20 @@ 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+
def weakGetOrElseUpdate(key: K)(value: => V): V =
37+
weakGet(key) match {
38+
case Some(result) => result
39+
case None =>
40+
val result = value
41+
this(key) = WeakReference(result)
42+
result
43+
}
44+
}
3145

3246
// fixups and maps prevent stackoverflows in importer
3347
var pendingSyms = 0
@@ -44,79 +58,81 @@ trait Importers extends api.Importers { self: SymbolTable =>
4458

4559
object reverse extends from.StandardImporter {
4660
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))
61+
// FIXME this and reverse should be constantly kept in sync
62+
// not just synced once upon the first usage of reverse
63+
for ((fromsym, WeakReference(mysym)) <- StandardImporter.this.symMap) symMap += ((mysym, WeakReference(fromsym)))
64+
for ((fromtpe, WeakReference(mytpe)) <- StandardImporter.this.tpeMap) tpeMap += ((mytpe, WeakReference(fromtpe)))
4965
}
5066

5167
// todo. careful import of positions
5268
def importPosition(pos: from.Position): Position =
5369
pos.asInstanceOf[Position]
5470

5571
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)
72+
def doImport(sym: from.Symbol): Symbol =
73+
symMap weakGet sym match {
74+
case Some(result) => result
75+
case _ =>
76+
val myowner = importSymbol(sym.owner)
77+
val mypos = importPosition(sym.pos)
78+
val myname = importName(sym.name).toTermName
79+
val myflags = sym.flags
80+
def linkReferenced(mysym: TermSymbol, x: from.TermSymbol, op: from.Symbol => Symbol): Symbol = {
81+
symMap.weakUpdate(x, mysym)
82+
mysym.referenced = op(x.referenced)
83+
mysym
8584
}
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)
85+
val mysym = sym match {
86+
case x: from.MethodSymbol =>
87+
linkReferenced(myowner.newMethod(myname, mypos, myflags), x, importSymbol)
88+
case x: from.ModuleSymbol =>
89+
linkReferenced(myowner.newModuleSymbol(myname, mypos, myflags), x, importSymbol)
90+
case x: from.FreeTermSymbol =>
91+
newFreeTermSymbol(importName(x.name).toTermName, x.value, x.flags, x.origin) setInfo importType(x.info)
92+
case x: from.FreeTypeSymbol =>
93+
newFreeTypeSymbol(importName(x.name).toTypeName, x.flags, x.origin)
94+
case x: from.TermSymbol =>
95+
linkReferenced(myowner.newValue(myname, mypos, myflags), x, importSymbol)
96+
case x: from.TypeSkolem =>
97+
val origin = x.unpackLocation match {
98+
case null => null
99+
case y: from.Tree => importTree(y)
100+
case y: from.Symbol => importSymbol(y)
101+
}
102+
myowner.newTypeSkolemSymbol(myname.toTypeName, origin, mypos, myflags)
103+
case x: from.ModuleClassSymbol =>
104+
val mysym = myowner.newModuleClass(myname.toTypeName, mypos, myflags)
105+
symMap.weakUpdate(x, mysym)
106+
mysym.sourceModule = importSymbol(x.sourceModule)
107+
mysym
108+
case x: from.ClassSymbol =>
109+
val mysym = myowner.newClassSymbol(myname.toTypeName, mypos, myflags)
110+
symMap.weakUpdate(x, mysym)
111+
if (sym.thisSym != sym) {
112+
mysym.typeOfThis = importType(sym.typeOfThis)
113+
mysym.thisSym setName importName(sym.thisSym.name)
114+
}
115+
mysym
116+
case x: from.TypeSymbol =>
117+
myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
98118
}
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) {
108-
override def complete(s: Symbol) {
109-
val result = sym.info match {
110-
case from.PolyType(_, res) => res
111-
case result => result
119+
symMap.weakUpdate(sym, mysym)
120+
mysym setFlag Flags.LOCKED
121+
mysym setInfo {
122+
val mytypeParams = sym.typeParams map importSymbol
123+
new LazyPolyType(mytypeParams) {
124+
override def complete(s: Symbol) {
125+
val result = sym.info match {
126+
case from.PolyType(_, res) => res
127+
case result => result
128+
}
129+
s setInfo GenPolyType(mytypeParams, importType(result))
130+
s setAnnotations (sym.annotations map importAnnotationInfo)
131+
}
112132
}
113-
s setInfo GenPolyType(mytypeParams, importType(result))
114-
s setAnnotations (sym.annotations map importAnnotationInfo)
115133
}
116-
}
117-
}
118-
mysym resetFlag Flags.LOCKED
119-
} // end doImport
134+
mysym resetFlag Flags.LOCKED
135+
} // end doImport
120136

121137
def importOrRelink: Symbol = {
122138
val sym = sym0 // makes sym visible in the debugger
@@ -186,14 +202,10 @@ trait Importers extends api.Importers { self: SymbolTable =>
186202
} // end importOrRelink
187203

188204
val sym = sym0
189-
if (symMap contains sym) {
190-
symMap(sym)
191-
} else {
205+
symMap.weakGetOrElseUpdate(sym) {
192206
pendingSyms += 1
193-
194-
try {
195-
symMap getOrElseUpdate (sym, importOrRelink)
196-
} finally {
207+
try importOrRelink
208+
finally {
197209
pendingSyms -= 1
198210
tryFixup()
199211
}
@@ -258,14 +270,10 @@ trait Importers extends api.Importers { self: SymbolTable =>
258270
def importOrRelink: Type =
259271
doImport(tpe)
260272

261-
if (tpeMap contains tpe) {
262-
tpeMap(tpe)
263-
} else {
273+
tpeMap.weakGetOrElseUpdate(tpe) {
264274
pendingTpes += 1
265-
266-
try {
267-
tpeMap getOrElseUpdate (tpe, importOrRelink)
268-
} finally {
275+
try importOrRelink
276+
finally {
269277
pendingTpes -= 1
270278
tryFixup()
271279
}
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)