Skip to content

Commit 28c1bd4

Browse files
authored
Use DoubleLock for mutex on compiler bridge creation to avoid races between MillDaemonMain and ZincWorkerMain processes (#5761)
Hopefully fixes #5743. Will need to rebootstrap on this to stop the failures in our CI The previous in-memory lock is no longer sufficient for mutex in this code path, because now both the main Mill process and any `ZincWorkerMain` subprocess could both be trying to initialize compiler bridges in the same `compilerBridge.workspace` folder
1 parent 56bae4b commit 28c1bd4

File tree

1 file changed

+17
-4
lines changed

1 file changed

+17
-4
lines changed

libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import mill.api.JsonFormatters.*
44
import mill.api.PathRef
55
import mill.api.daemon.internal.CompileProblemReporter
66
import mill.api.daemon.{Logger, Result}
7+
import mill.client.lock.*
78
import mill.javalib.api.internal.{
89
JavaCompilerOptions,
910
ZincCompileJava,
@@ -36,7 +37,7 @@ class ZincWorker(
3637
jobs: Int
3738
) extends AutoCloseable { self =>
3839
private val incrementalCompiler = new sbt.internal.inc.IncrementalCompilerImpl()
39-
private val compilerBridgeLocks: mutable.Map[String, Object] = mutable.Map.empty[String, Object]
40+
private val compilerBridgeLocks: mutable.Map[String, MemoryLock] = mutable.Map.empty
4041

4142
private val classloaderCache = new RefCountedClassLoaderCache(
4243
sharedLoader = getClass.getClassLoader,
@@ -547,10 +548,22 @@ class ZincWorker(
547548
compilerBridge: ZincCompilerBridgeProvider
548549
): os.Path = {
549550
val workingDir = compilerBridge.workspace / s"zinc-${Versions.zinc}" / scalaVersion
550-
val lock = synchronized(compilerBridgeLocks.getOrElseUpdate(scalaVersion, new Object()))
551+
552+
os.makeDir.all(compilerBridge.workspace / "compiler-bridge-locks")
553+
val memoryLock = synchronized(
554+
compilerBridgeLocks.getOrElseUpdate(scalaVersion, new MemoryLock)
555+
)
551556
val compiledDest = workingDir / "compiled"
552557
val doneFile = compiledDest / "DONE"
553-
lock.synchronized {
558+
// Use a double-lock here because we need mutex both between threads within this
559+
// process, as well as between different processes since sometimes we are initializing
560+
// the compiler bridge inside a separate `ZincWorkerMain` subprocess
561+
val doubleLock = new DoubleLock(
562+
memoryLock,
563+
new FileLock((compilerBridge.workspace / "compiler-bridge-locks" / scalaVersion).toString)
564+
)
565+
try {
566+
doubleLock.lock()
554567
if (os.exists(doneFile)) compiledDest
555568
else {
556569
val acquired =
@@ -572,7 +585,7 @@ class ZincWorker(
572585
compiledDest
573586
}
574587
}
575-
}
588+
} finally doubleLock.close()
576589
}
577590
}
578591
object ZincWorker {

0 commit comments

Comments
 (0)