davidB / scala-maven-plugin

The scala-maven-plugin (previously maven-scala-plugin) is used for compiling/testing/running/documenting scala code in maven.
https://davidb.github.io/scala-maven-plugin/
The Unlicense
554 stars 150 forks source link

Race condition in sbt-compiler-bridge jar when running multimodule project with -T #738

Closed josephlbarnett closed 3 months ago

josephlbarnett commented 8 months ago

We have a somewhat large multi-module project using the scala-maven-plugin that we build with -T 4 to parallelize and speed up the build process. Sometimes in CI, we get the following error on a random module (and rerunning tends to work successfully):

Error:  ## Exception when compiling 2 sources to /workspace/commontest/target/classes
java.lang.ClassNotFoundException: xsbt.CompilerInterface
java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:476)
java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
java.base/java.lang.Class.forName0(Native Method)
java.base/java.lang.Class.forName(Class.java:398)
sbt.internal.inc.AnalyzingCompiler.getInterfaceClass(AnalyzingCompiler.scala:278)
sbt.internal.inc.AnalyzingCompiler.call(AnalyzingCompiler.scala:245)
sbt.internal.inc.AnalyzingCompiler.newCachedCompiler(AnalyzingCompiler.scala:145)
sbt.internal.inc.AnalyzingCompiler.newCachedCompiler(AnalyzingCompiler.scala:132)
sbt.internal.inc.FreshCompilerCache.apply(CompilerCache.scala:102)
sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:92)
sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:91)
scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:186)
sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$3(MixedAnalyzingCompiler.scala:82)
sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$3$adapted(MixedAnalyzingCompiler.scala:77)
sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:215)
sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:77)
sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:146)
sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:343)
sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:343)
sbt.internal.inc.Incremental$.doCompile(Incremental.scala:120)
sbt.internal.inc.Incremental$.$anonfun$compile$4(Incremental.scala:100)
sbt.internal.inc.IncrementalCommon.recompileClasses(IncrementalCommon.scala:180)
sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:98)
sbt.internal.inc.Incremental$.$anonfun$compile$3(Incremental.scala:102)
sbt.internal.inc.Incremental$.manageClassfiles(Incremental.scala:155)
sbt.internal.inc.Incremental$.compile(Incremental.scala:92)
sbt.internal.inc.IncrementalCompile$.apply(Compile.scala:75)
sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:348)
sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:301)
sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:168)
sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:248)
sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:74)
sbt_inc.SbtIncrementalCompiler.compile(SbtIncrementalCompiler.java:172)
scala_maven.ScalaCompilerSupport.incrementalCompile(ScalaCompilerSupport.java:291)
scala_maven.ScalaCompilerSupport.compile(ScalaCompilerSupport.java:110)
scala_maven.ScalaCompilerSupport.doExecute(ScalaCompilerSupport.java:92)
scala_maven.ScalaMojoSupport.execute(ScalaMojoSupport.java:557)
org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:210)
org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:156)
org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:148)
org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder$1.call(MultiThreadedBuilder.java:190)
org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder$1.call(MultiThreadedBuilder.java:186)
java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
java.base/java.lang.Thread.run(Thread.java:829)

We think what's happening is that multiple modules are attempting to compile the sbt-compiler-bridge jar and install it to ~/.sbt/1.0/zinc/org.scala-sbt, and when a later one finishes doing so, it replaces the jar that is in use by another module that's attempting to compile scala code having already referenced the existing jarfile. (in at least one failed run, 3 modules attempt and succeed in compiling and installing the bridge. Just before they do that installation, a 4th module logs out "Compiler bridge file is not installed yet" and starts to compile and install it again. Just as that module finishes installing the bridge, a separate module fails with the error above)

We've worked around this for now by adding an empty src/test/scala file in the parent pom-only module which forces the compiler bridge installation to happen before the child modules get threaded off, but would prefer not to need that ugly workaround.

slandelle commented 8 months ago

One possibility would be to modify this plugin's code and synchronize the compiler bridge installation. Have you tried this?

josephlbarnett commented 8 months ago

Have not tried due to the rarity of failures and the infrastructure setup of installing a modified plugin somewhere CI can access it, but probably could just make CompilerBridgeFactory.getCompiledBridgeJar synchronized to try this?

slandelle commented 3 months ago

Closed by #759