[2.0.x] fix: keep worker classloader open for shutdown hooks (#9639)

This commit is contained in:
Stas Shevchenko
2026-08-26 18:38:19 -04:00
committed by Eugene Yokota
parent 5cee896c5f
commit 24324be9e5
5 changed files with 55 additions and 9 deletions
@@ -0,0 +1,21 @@
Global / localCacheDirectory := baseDirectory.value / "diskcache"
scalaVersion := "3.8.4"
Test / fork := true
libraryDependencies += "org.scalameta" %% "munit" % "1.0.4" % Test
val check = TaskKey[Unit]("check", "Verify the shutdown hook could load classes.")
check := Def.uncached {
val file = baseDirectory.value / "hook-result.txt"
val deadline = System.currentTimeMillis + 30000
def content: String = if (file.exists) IO.read(file).trim else ""
while (content.isEmpty || content.startsWith("PENDING")) {
if (System.currentTimeMillis > deadline)
sys.error(s"shutdown hook never completed: '$content'")
Thread.sleep(500)
}
val result = content
if (!result.startsWith("OK")) sys.error(s"shutdown hook failed: '$result'")
}
@@ -0,0 +1,5 @@
package repro
object LazilyLoaded {
val marker: String = "loaded"
}
@@ -0,0 +1,20 @@
package repro
import java.nio.file.{ Files, Paths }
class ShutdownHookTest extends munit.FunSuite {
test("shutdown hook can load classes after the test run") {
val out = Paths.get("hook-result.txt")
Files.writeString(out, "PENDING: shutdown hook did not run" + System.lineSeparator)
val _ = sys.addShutdownHook {
val result =
try {
Class.forName("repro.LazilyLoaded$")
"OK: classloader still works at JVM shutdown"
} catch {
case t: Throwable => s"FAIL: $t"
}
Files.writeString(out, result + System.lineSeparator)
}
}
}
@@ -0,0 +1,2 @@
> testFull
> check
@@ -188,12 +188,11 @@ public final class WorkerMain {
throw new RuntimeException("missing jvmRunInfo element");
}
RunInfo.JvmRunInfo jvmRunInfo = info.jvmRunInfo;
try (URLClassLoader cl = createClassLoader(jvmRunInfo, ClassLoader.getSystemClassLoader())) {
Class<?> mainClass = cl.loadClass(jvmRunInfo.mainClass);
Method mainMethod = mainClass.getMethod("main", String[].class);
String[] mainArgs = jvmRunInfo.args.stream().toArray(String[]::new);
mainMethod.invoke(null, (Object) mainArgs);
}
URLClassLoader cl = createClassLoader(jvmRunInfo, ClassLoader.getSystemClassLoader());
Class<?> mainClass = cl.loadClass(jvmRunInfo.mainClass);
Method mainMethod = mainClass.getMethod("main", String[].class);
String[] mainArgs = jvmRunInfo.args.stream().toArray(String[]::new);
mainMethod.invoke(null, (Object) mainArgs);
} else {
throw new RuntimeException("only jvm is supported");
}
@@ -207,9 +206,8 @@ public final class WorkerMain {
if (jvmRunInfo.classpath.isEmpty()) {
ForkTestMain.main(id, info, this.jsonOut, parent);
} else {
try (URLClassLoader cl = createClassLoader(jvmRunInfo, parent)) {
ForkTestMain.main(id, info, this.jsonOut, cl);
}
URLClassLoader cl = createClassLoader(jvmRunInfo, parent);
ForkTestMain.main(id, info, this.jsonOut, cl);
}
} else {
throw new RuntimeException("only jvm is supported");