Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@lihaoyi
Copy link
Member

@lihaoyi lihaoyi commented Aug 6, 2025

Seems to be caused by MillBuildBootstrap closing the classloader without closing the workers, resulting in some of the workers in workerCache being loaded by an already-closed classloader, such that if they try to load a new class during teardown it fails with a NoClassDefFoundError

> ./mill 'example.fundamentals.out-dir[1-out-files].packaged.daemon'
...
[6507] ------------------------------
[6507] /Users/lihaoyi/Github/mill/out/example/fundamentals/out-dir/1-out-files/packaged/daemon/testForked.dest/sandbox/run-1> sed -i.bak 's/{}/{println(123)}/g' build.mill
[6507] --- Expected output ----------
[6507] 
[6507] ------------------------------
[6507] /Users/lihaoyi/Github/mill/out/example/fundamentals/out-dir/1-out-files/packaged/daemon/testForked.dest/sandbox/run-1> ./mill  --ticker false foo.compile
[6507] --- Expected output ----------
[6507] 
[6507] ------------------------------
[6507] /Users/lihaoyi/Github/mill/out/example/fundamentals/out-dir/1-out-files/packaged/daemon/testForked.dest/sandbox/run-1> ./mill  --ticker false shutdown
[6507] --- Expected output ----------
[6507] 
[6507] ------------------------------
[6507] X mill.testkit.UtestExampleTestSuite.exampleTest 13499ms 
[6507]   utest.AssertionError: evalResult.isSuccess
[6507]   evalResult: EvalResult = EvalResult(
[6507]     exitCode = 1,
[6507]     out = """[info] compiling 1 Scala source to /Users/lihaoyi/Github/mill/out/example/fundamentals/out-dir/1-out-files/packaged/daemon/testForked.dest/sandbox/run-1/out/mill-build/compile.dest/classes ...
[6507]   [info] done compiling
[6507]   123
[6507]   java.lang.Exception: java.lang.NoClassDefFoundError: mill/util/RefCountedClassLoaderCache$Foo
[6507]   	at mill.exec.Execution.evaluateTerminals$1$$anonfun$1$$anonfun$1(Execution.scala:256)
[6507]   	at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467)
[6507]   	at mill.exec.ExecutionContexts$.execute$$anonfun$1$$anonfun$2$$anonfun$1(ExecutionContexts.scala:65)
[6507]   	at mill.exec.ExecutionContexts$.mill$exec$ExecutionContexts$ThreadPool$$_$execute$$anonfun$1$$anonfun$2$$anonfun$adapted$1(ExecutionContexts.scala:66)
[6507]   	at mill.api.SystemStreamsUtils$.withStreams$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1(SystemStreamsUtils.scala:49)
[6507]   	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:59)
[6507]   	at mill.api.SystemStreamsUtils$.withStreams$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1(SystemStreamsUtils.scala:50)
[6507]   	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:59)
[6507]   	at mill.api.SystemStreamsUtils$.withStreams$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1(SystemStreamsUtils.scala:51)
[6507]   	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:59)
[6507]   	at mill.api.SystemStreamsUtils$.withStreams$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1(SystemStreamsUtils.scala:52)
[6507]   	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:59)
[6507]   	at scala.Console$.withErr(Console.scala:193)
[6507]   	at mill.api.SystemStreamsUtils$.withStreams$$anonfun$1$$anonfun$1$$anonfun$1(SystemStreamsUtils.scala:53)
[6507]   ...
[6507]     utest.asserts.Asserts$.assertImpl(Asserts.scala:30)
[6507]     mill.testkit.ExampleTester.validateEval(ExampleTester.scala:161)
[6507]     mill.testkit.ExampleTester.processCommand(ExampleTester.scala:149)
[6507]     mill.testkit.ExampleTester.processCommandBlock(ExampleTester.scala:98)
[6507]     mill.testkit.ExampleTester.run$$anonfun$1(ExampleTester.scala:219)
[6507]     scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[6507]     scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[6507]     scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1324)
[6507]     mill.testkit.ExampleTester.run(ExampleTester.scala:219)
[6507]     mill.testkit.ExampleTester$.run(ExampleTester.scala:63)
[6507]     mill.testkit.UtestExampleTestSuite$.run$1$$anonfun$1(UtestExampleTestSuite.scala:24)
[6507]     mill.util.Retry.apply$$anonfun$1(Retry.scala:38)
[6507]     mill.util.Retry.apply$$anonfun$adapted$1(Retry.scala:38)
[6507]     mill.util.Retry.$anonfun$1$$anonfun$1(Retry.scala:49)
[6507]     scala.util.Try$.apply(Try.scala:217)
[6507]     mill.util.Retry.$anonfun$1(Retry.scala:49)
[6507]     java.lang.Thread.run(Thread.java:840)
[6507] Tests: 1, Passed: 0, Failed: 1

The solution is that in MillBuildBootstrap, before we close each classloader, we need to first close all the workers for the next frame's workerCache, and set the workerCache to Map.empty when we later create the evaluator. This ensures that the workers get closed after the classloader that loaded them got closed, avoiding the NoClassDefFound errors caused by the worker close() code trying to load classes from a closed classloader

Added an integration test to specifically exercise this case, that defines a worker with a class that is only used in .close() . This fails on main, and seems to pass on this PR

@lihaoyi lihaoyi changed the title Repro classloading error Repro classloading error on worker teardown after code change Aug 6, 2025
@lihaoyi lihaoyi force-pushed the repro-class-error branch from 38b9197 to ec66a2b Compare August 6, 2025 09:22
@lihaoyi lihaoyi force-pushed the repro-class-error branch from ee4ebd3 to 06a77c9 Compare August 6, 2025 14:09
@lihaoyi lihaoyi changed the title Repro classloading error on worker teardown after code change Fix classloading error on worker teardown after code change Aug 6, 2025
@lihaoyi lihaoyi merged commit 1cb0859 into com-lihaoyi:main Aug 6, 2025
32 of 33 checks passed
@lefou lefou added this to the 1.0.4 milestone Aug 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants