Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
josephlbarnett opened this issue Nov 27, 2023 · 3 comments

Comments

@josephlbarnett
Copy link

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
Copy link
Collaborator

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

@josephlbarnett
Copy link
Author

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?

acote-coveo added a commit to acote-coveo/scala-maven-plugin that referenced this issue Apr 22, 2024
It was identified in davidB#738 that a race condition could occur when building a project using multiple threads.

To mitigate the issue, I wrapped the installation of the compiler with a synchronized block on the Static instance.

As a result, if multiple threads try to install the compiler at the same time, only one thread will be able to do so. When they will get unlocked, it will check if the file was created meanwhile and if so, to will early return.
slandelle pushed a commit that referenced this issue Apr 23, 2024
It was identified in #738 that a race condition could occur when building a project using multiple threads.

To mitigate the issue, I wrapped the installation of the compiler with a synchronized block on the Static instance.

As a result, if multiple threads try to install the compiler at the same time, only one thread will be able to do so. When they will get unlocked, it will check if the file was created meanwhile and if so, to will early return.
@slandelle
Copy link
Collaborator

slandelle commented Apr 23, 2024

Closed by #759

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

No branches or pull requests

2 participants