From dd7b74a454771e00c15ac610737e37655a710800 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 13 Jul 2018 15:25:44 +1000 Subject: [PATCH 1/2] Cleanup after tests to avoid OOME: Metaspace in fast-run testOnly sequence After these changes, this project no longer exhibits the problems discussed in https://github.com/sbt/sbt/issues/2056 --- build.sbt | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 591b02d1..5853b0a0 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,11 @@ def commonSettings: Seq[Setting[_]] = Def.settings( inCompileAndTest(scalacOptions in console --= Vector("-Ywarn-unused-import", "-Ywarn-unused", "-Xlint")), publishArtifact in Compile := true, publishArtifact in Test := false, - parallelExecution in Test := false + parallelExecution in Test := false, + testOptions in Test += { + val log = streams.value.log + Tests.Cleanup { loader => cleanupTests(loader, log) } + } ) val mimaSettings = Def settings ( @@ -275,3 +279,22 @@ inThisBuild(Seq( def inCompileAndTest(ss: SettingsDefinition*): Seq[Setting[_]] = Seq(Compile, Test) flatMap (inConfig(_)(Def.settings(ss: _*))) + + +// TODO move into sbt-house-rules? +def cleanupTests(loader: ClassLoader, log: sbt.internal.util.ManagedLogger): Unit = { + // shutdown Log4J to avoid classloader leaks + try { + val logManager = Class.forName("org.apache.logging.log4j.LogManager") + logManager.getMethod("shutdown").invoke(null) + } catch { + case _: Throwable => + log.warn("Could not shut down Log4J") + } + // Scala Test loads property bundles, let's eagerly clear then from the internal cache + // TODO move into SBT itself? + java.util.ResourceBundle.clearCache(loader) + // Scala Test also starts TimerThreads that it doesn't eagerly cancel. This can weakly retain + // metaspace until a full GC. + System.gc() +} From 50b57effb9c4b4aa4a9a99af08f12e60d57b0d07 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 16 Jul 2018 10:19:01 +1000 Subject: [PATCH 2/2] Cleanup Log4J in the test classloader, not SBT's --- build.sbt | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 5853b0a0..e6472873 100644 --- a/build.sbt +++ b/build.sbt @@ -30,11 +30,7 @@ def commonSettings: Seq[Setting[_]] = Def.settings( inCompileAndTest(scalacOptions in console --= Vector("-Ywarn-unused-import", "-Ywarn-unused", "-Xlint")), publishArtifact in Compile := true, publishArtifact in Test := false, - parallelExecution in Test := false, - testOptions in Test += { - val log = streams.value.log - Tests.Cleanup { loader => cleanupTests(loader, log) } - } + parallelExecution in Test := false ) val mimaSettings = Def settings ( @@ -106,6 +102,10 @@ lazy val lmCore = (project in file("core")) managedSourceDirectories in Compile += baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", + testOptions in Test += { + val log = streams.value.log + Tests.Cleanup { loader => cleanupTests(loader, log) } + }, contrabandFormatsForType in generateContrabands in Compile := DatatypeConfig.getFormats, // WORKAROUND sbt/sbt#2205 include managed sources in packageSrc mappings in (Compile, packageSrc) ++= { @@ -231,6 +231,10 @@ lazy val lmIvy = (project in file("ivy")) contrabandFormatsForType in generateContrabands in Compile := DatatypeConfig.getFormats, scalacOptions in (Compile, console) --= Vector("-Ywarn-unused-import", "-Ywarn-unused", "-Xlint"), + testOptions in Test += { + val log = streams.value.log + Tests.Cleanup { loader => cleanupTests(loader, log) } + }, mimaSettings, mimaBinaryIssueFilters ++= Seq( exclude[DirectMissingMethodProblem]("sbt.internal.librarymanagement.ivyint.GigahorseUrlHandler#SbtUrlInfo.this"), @@ -285,11 +289,15 @@ def inCompileAndTest(ss: SettingsDefinition*): Seq[Setting[_]] = def cleanupTests(loader: ClassLoader, log: sbt.internal.util.ManagedLogger): Unit = { // shutdown Log4J to avoid classloader leaks try { - val logManager = Class.forName("org.apache.logging.log4j.LogManager") + val logManager = Class.forName("org.apache.logging.log4j.LogManager", false, loader) logManager.getMethod("shutdown").invoke(null) + log.debug("Log4J2 Shutdown successful!") } catch { - case _: Throwable => - log.warn("Could not shut down Log4J") + case _: ClassNotFoundException => + // not in this classloader + case t: Throwable => + log.debug("Could not shut down Log4J") + log.trace(t) } // Scala Test loads property bundles, let's eagerly clear then from the internal cache // TODO move into SBT itself?