From 3b6b0259a60289946b0c97122e68983fdc54cb51 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 9 Dec 2024 23:24:01 -0500 Subject: [PATCH] fix: concurrency control around Scala 3 compiler **Problem** Early initialization thread throws ConcurrentModificationException. **Solution** This puts in synchronized to lock for the access. --- .../scala/sbt/internal/parser/SbtParser.scala | 28 ++++++++++--------- .../sbt/internal/parser/SbtRefactorings.scala | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala b/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala index b014616de8..36a00dc164 100644 --- a/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/buildfile/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -131,8 +131,10 @@ private[sbt] object SbtParser: private[sbt] var scalacGlobalInitReporter: Option[ConsoleReporter] = None - private[sbt] val globalReporter = UniqueParserReporter() - private[sbt] val defaultGlobalForParser = ParseDriver() + private[sbt] lazy val globalReporter = UniqueParserReporter() + private lazy val defaultGlobalForParser = ParseDriver() + private[sbt] def getGlobalForParser: ParseDriver = synchronized: + defaultGlobalForParser private[sbt] final class ParseDriver extends Driver: override protected val sourcesRequired: Boolean = false val compileCtx0 = initCtx.fresh @@ -171,15 +173,17 @@ private[sbt] object SbtParser: parsedTrees end SbtParser -private class SbtParserInit { - new Thread("sbt-parser-init-thread") { +/** + * This gives JVM a head start to JIT Scala 3 compiler JAR. + * Called by sbt.internal.ClassLoaderWarmup. + */ +private class SbtParserInit: + val t = new Thread("sbt-parser-init-thread"): setDaemon(true) - start() - override def run(): Unit = { - val _ = SbtParser.defaultGlobalForParser - } - } -} + override def run(): Unit = + val _ = SbtParser.getGlobalForParser + t.start() +end SbtParserInit /** * This method solely exists to add scaladoc to members in SbtParser which @@ -222,8 +226,6 @@ private[sbt] case class SbtParser(path: VirtualFileRef, lines: Seq[String]) // parsed trees. val (imports, settings, settingsTrees) = splitExpressions(path, lines) - import SbtParser.defaultGlobalForParser.* - private def splitExpressions( path: VirtualFileRef, lines: Seq[String] @@ -239,7 +241,7 @@ private[sbt] case class SbtParser(path: VirtualFileRef, lines: Seq[String]) VirtualFile(reporterId, wrapCode.getBytes(StandardCharsets.UTF_8)), scala.io.Codec.UTF8 ) - given Context = compileCtx.fresh.setSource(sourceFile) + given Context = SbtParser.getGlobalForParser.compileCtx.fresh.setSource(sourceFile) val parsedTrees = parse(fileName, reporterId) // Check No val (a,b) = foo *or* val a,b = foo as these are problematic to range positions and the WHOLE architecture. diff --git a/buildfile/src/main/scala/sbt/internal/parser/SbtRefactorings.scala b/buildfile/src/main/scala/sbt/internal/parser/SbtRefactorings.scala index 6817cb85eb..cf3593c3a8 100644 --- a/buildfile/src/main/scala/sbt/internal/parser/SbtRefactorings.scala +++ b/buildfile/src/main/scala/sbt/internal/parser/SbtRefactorings.scala @@ -35,7 +35,7 @@ private[sbt] object SbtRefactorings: commands: Seq[SessionSetting] ): Seq[String] = { val split = SbtParser(FAKE_FILE, lines) - given ctx: Context = SbtParser.defaultGlobalForParser.compileCtx + given ctx: Context = SbtParser.getGlobalForParser.compileCtx val recordedCommands = recordCommands(commands, split) val sortedRecordedCommands = recordedCommands.sortBy(_._1)(reverseOrderingInt)