Skip to content

Commit cfbb2a2

Browse files
committed
[backport] Resource management for macro/plugin classloaders, classpath JARs
Backports: - scala#7366 - scala#7644
1 parent 9e6ca45 commit cfbb2a2

28 files changed

+486
-223
lines changed

src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala

+1-12
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,8 @@ trait MacroRuntimes extends JavaReflectionRuntimes {
5454
/** Macro classloader that is used to resolve and run macro implementations.
5555
* Loads classes from from -cp (aka the library classpath).
5656
* Is also capable of detecting REPL and reusing its classloader.
57-
*
58-
* When -Xmacro-jit is enabled, we sometimes fallback to on-the-fly compilation of macro implementations,
59-
* which compiles implementations into a virtual directory (very much like REPL does) and then conjures
60-
* a classloader mapped to that virtual directory.
6157
*/
62-
private lazy val defaultMacroClassloaderCache = {
63-
def attemptClose(loader: ClassLoader): Unit = loader match {
64-
case u: URLClassLoader => debuglog("Closing macro runtime classloader"); u.close()
65-
case afcl: AbstractFileClassLoader => attemptClose(afcl.getParent)
66-
case _ => ???
67-
}
68-
perRunCaches.newGeneric(findMacroClassLoader, attemptClose _)
69-
}
58+
private lazy val defaultMacroClassloaderCache: () => ClassLoader = perRunCaches.newGeneric(findMacroClassLoader())
7059
def defaultMacroClassloader: ClassLoader = defaultMacroClassloaderCache()
7160

7261
/** Abstracts away resolution of macro runtimes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.tools.nsc
14+
15+
import scala.util.control.NonFatal
16+
17+
/** Registry for resources to close when `Global` is closed */
18+
final class CloseableRegistry {
19+
private[this] var closeables: List[java.io.Closeable] = Nil
20+
final def registerClosable(c: java.io.Closeable): Unit = {
21+
closeables ::= c
22+
}
23+
24+
def close(): Unit = {
25+
for (c <- closeables) {
26+
try {
27+
c.close()
28+
} catch {
29+
case NonFatal(_) =>
30+
}
31+
}
32+
closeables = Nil
33+
}
34+
}

src/compiler/scala/tools/nsc/GenericRunnerSettings.scala

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ import java.net.URL
1616
import scala.tools.util.PathResolver
1717

1818
class GenericRunnerSettings(error: String => Unit) extends Settings(error) {
19-
lazy val classpathURLs: Seq[URL] = new PathResolver(this).resultAsURLs
19+
lazy val classpathURLs: Seq[URL] = {
20+
val registry = new CloseableRegistry
21+
try {
22+
new PathResolver(this, registry).resultAsURLs
23+
} finally {
24+
registry.close()
25+
}
26+
}
2027

2128
val howtorun =
2229
ChoiceSetting(

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ import scala.language.postfixOps
4040
import scala.tools.nsc.ast.{TreeGen => AstTreeGen}
4141
import scala.tools.nsc.classpath._
4242
import scala.tools.nsc.profile.Profiler
43+
import java.io.Closeable
4344

4445
class Global(var currentSettings: Settings, reporter0: Reporter)
4546
extends SymbolTable
47+
with Closeable
4648
with CompilationUnits
4749
with Plugins
4850
with PhaseAssembly
@@ -817,7 +819,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
817819

818820
/** Extend classpath of `platform` and rescan updated packages. */
819821
def extendCompilerClassPath(urls: URL*): Unit = {
820-
val urlClasspaths = urls.map(u => ClassPathFactory.newClassPath(AbstractFile.getURL(u), settings))
822+
val urlClasspaths = urls.map(u => ClassPathFactory.newClassPath(AbstractFile.getURL(u), settings, closeableRegistry))
821823
val newClassPath = AggregateClassPath.createAggregate(platform.classPath +: urlClasspaths : _*)
822824
platform.currentClassPath = Some(newClassPath)
823825
invalidateClassPathEntries(urls.map(_.getPath): _*)
@@ -879,7 +881,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
879881
}
880882
entries(classPath) find matchesCanonical match {
881883
case Some(oldEntry) =>
882-
Some(oldEntry -> ClassPathFactory.newClassPath(dir, settings))
884+
Some(oldEntry -> ClassPathFactory.newClassPath(dir, settings, closeableRegistry))
883885
case None =>
884886
error(s"Error adding entry to classpath. During invalidation, no entry named $path in classpath $classPath")
885887
None
@@ -1706,6 +1708,13 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
17061708
}
17071709

17081710
def createJavadoc = false
1711+
1712+
final val closeableRegistry: CloseableRegistry = new CloseableRegistry
1713+
1714+
def close(): Unit = {
1715+
perRunCaches.clearAll()
1716+
closeableRegistry.close()
1717+
}
17091718
}
17101719

17111720
object Global {

src/compiler/scala/tools/nsc/backend/JavaPlatform.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait JavaPlatform extends Platform {
2727
private[nsc] var currentClassPath: Option[ClassPath] = None
2828

2929
protected[nsc] def classPath: ClassPath = {
30-
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result)
30+
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings, global.closeableRegistry).result)
3131
currentClassPath.get
3232
}
3333

src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ package scala.tools.nsc.classpath
1414

1515
import scala.reflect.io.{AbstractFile, VirtualDirectory}
1616
import scala.reflect.io.Path.string2path
17-
import scala.tools.nsc.Settings
17+
import scala.tools.nsc.{CloseableRegistry, Settings}
1818
import FileUtils.AbstractFileOps
1919
import scala.tools.nsc.util.ClassPath
2020

2121
/**
2222
* Provides factory methods for classpath. When creating classpath instances for a given path,
2323
* it uses proper type of classpath depending on a types of particular files containing sources or classes.
2424
*/
25-
class ClassPathFactory(settings: Settings) {
25+
class ClassPathFactory(settings: Settings, closeableRegistry: CloseableRegistry) {
2626
/**
2727
* Create a new classpath based on the abstract file.
2828
*/
29-
def newClassPath(file: AbstractFile): ClassPath = ClassPathFactory.newClassPath(file, settings)
29+
def newClassPath(file: AbstractFile): ClassPath = ClassPathFactory.newClassPath(file, settings, closeableRegistry)
3030

3131
/**
3232
* Creators for sub classpaths which preserve this context.
@@ -70,19 +70,19 @@ class ClassPathFactory(settings: Settings) {
7070

7171
private def createSourcePath(file: AbstractFile): ClassPath =
7272
if (file.isJarOrZip)
73-
ZipAndJarSourcePathFactory.create(file, settings)
73+
ZipAndJarSourcePathFactory.create(file, settings, closeableRegistry)
7474
else if (file.isDirectory)
7575
DirectorySourcePath(file.file)
7676
else
7777
sys.error(s"Unsupported sourcepath element: $file")
7878
}
7979

8080
object ClassPathFactory {
81-
def newClassPath(file: AbstractFile, settings: Settings): ClassPath = file match {
81+
def newClassPath(file: AbstractFile, settings: Settings, closeableRegistry: CloseableRegistry): ClassPath = file match {
8282
case vd: VirtualDirectory => VirtualDirectoryClassPath(vd)
8383
case _ =>
8484
if (file.isJarOrZip)
85-
ZipAndJarClassPathFactory.create(file, settings)
85+
ZipAndJarClassPathFactory.create(file, settings, closeableRegistry)
8686
else if (file.isDirectory)
8787
DirectoryClassPath(file.file)
8888
else

src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala

+11-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
package scala.tools.nsc.classpath
1414

15-
import java.io.File
15+
import java.io.{Closeable, File}
1616
import java.net.{URI, URL}
1717
import java.nio.file.{FileSystems, Files, SimpleFileVisitor}
1818
import java.util.function.IntFunction
@@ -25,6 +25,7 @@ import FileUtils._
2525
import scala.collection.JavaConverters._
2626
import scala.collection.immutable
2727
import scala.reflect.internal.JDK9Reflectors
28+
import scala.tools.nsc.CloseableRegistry
2829
import scala.tools.nsc.classpath.PackageNameUtils.{packageContains, separatePkgAndClassNames}
2930

3031
/**
@@ -61,6 +62,7 @@ trait DirectoryLookup[FileEntryType <: ClassRepresentation] extends ClassPath {
6162

6263
private[nsc] def packages(inPackage: String): Seq[PackageEntry] = {
6364
val dirForPackage = getDirectory(inPackage)
65+
6466
val nestedDirs: Array[F] = dirForPackage match {
6567
case None => emptyFiles
6668
case Some(directory) => listChildren(directory, Some(isPackage))
@@ -137,7 +139,7 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
137139

138140
object JrtClassPath {
139141
import java.nio.file._, java.net.URI
140-
def apply(release: Option[String]): Option[ClassPath] = {
142+
def apply(release: Option[String], closeableRegistry: CloseableRegistry): Option[ClassPath] = {
141143
import scala.util.Properties._
142144
if (!isJavaAtLeast("9")) None
143145
else {
@@ -154,7 +156,11 @@ object JrtClassPath {
154156
try {
155157
val ctSym = Paths.get(javaHome).resolve("lib").resolve("ct.sym")
156158
if (Files.notExists(ctSym)) None
157-
else Some(new CtSymClassPath(ctSym, v.toInt))
159+
else {
160+
val classPath = new CtSymClassPath(ctSym, v.toInt)
161+
closeableRegistry.registerClosable(classPath)
162+
Some(classPath)
163+
}
158164
} catch {
159165
case _: Throwable => None
160166
}
@@ -230,7 +236,7 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
230236
/**
231237
* Implementation `ClassPath` based on the $JAVA_HOME/lib/ct.sym backing http://openjdk.java.net/jeps/247
232238
*/
233-
final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends ClassPath with NoSourcePaths {
239+
final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends ClassPath with NoSourcePaths with Closeable {
234240
import java.nio.file.Path, java.nio.file._
235241

236242
private val fileSystem: FileSystem = FileSystems.newFileSystem(ctSym, null)
@@ -278,7 +284,7 @@ final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends Clas
278284

279285
def asURLs: Seq[URL] = Nil
280286
def asClassPathStrings: Seq[String] = Nil
281-
287+
override def close(): Unit = fileSystem.close()
282288
def findClassFile(className: String): Option[AbstractFile] = {
283289
if (!className.contains(".")) None
284290
else {

0 commit comments

Comments
 (0)