From e0457697383ab9738a11bf5b2afa8ab5a5490da5 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 1 Oct 2024 12:12:12 +0200 Subject: [PATCH 1/4] Optimize `SoftReference` caches using `ConcurrentHashMap`'s `compute` --- .../scalajslib/worker/ScalaJSWorkerImpl.scala | 31 ++-- .../mill/scalalib/worker/ZincWorkerImpl.scala | 133 ++++++++++-------- 2 files changed, 89 insertions(+), 75 deletions(-) diff --git a/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala b/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala index d6f2164ff72..06c7be62882 100644 --- a/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala +++ b/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala @@ -22,7 +22,6 @@ import org.scalajs.jsenv.{Input, JSEnv, RunConfig} import org.scalajs.testing.adapter.TestAdapter import org.scalajs.testing.adapter.{TestAdapterInitializer => TAI} -import scala.collection.mutable import scala.ref.SoftReference import com.armanbilge.sjsimportmap.ImportMappedIRFile @@ -44,16 +43,22 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi { case _ => true } private object ScalaJSLinker { - private val irFileCache = StandardImpl.irFileCache() - private val cache = mutable.Map.empty[LinkerInput, SoftReference[(Linker, IRFileCache.Cache)]] - def reuseOrCreate(input: LinkerInput): (Linker, IRFileCache.Cache) = cache.get(input) match { - case Some(SoftReference((linker, irFileCacheCache))) => (linker, irFileCacheCache) - case _ => - val newResult = createLinker(input) - cache.update(input, SoftReference(newResult)) - newResult + private class Cache[A, B <: AnyRef] { + protected val cache = new java.util.concurrent.ConcurrentHashMap[A, SoftReference[B]] + + def getOrElseCreate(a: A)(create: => B) = + cache.compute( + a, + { + case (_, v @ SoftReference(_)) => v + case _ => SoftReference(create) + } + )() } - private def createLinker(input: LinkerInput): (Linker, IRFileCache.Cache) = { + private val irFileCache = StandardImpl.irFileCache() + private val cache = new Cache[LinkerInput, (Linker, IRFileCache.Cache)] + + def reuseOrCreate(input: LinkerInput): (Linker, IRFileCache.Cache) = cache.getOrElseCreate(input) { val semantics = input.isFullLinkJS match { case true => Semantics.Defaults.optimized case false => Semantics.Defaults @@ -234,16 +239,16 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi { if (useLegacy) { val jsFileName = "out.js" val jsFile = new File(dest, jsFileName).toPath() - var linkerOutput = LinkerOutput(PathOutputFile(jsFile)) + var linkerOutput = (LinkerOutput(PathOutputFile(jsFile)): @scala.annotation.nowarn("cat=deprecation")) .withJSFileURI(java.net.URI.create(jsFile.getFileName.toString)) val sourceMapNameOpt = Option.when(sourceMap)(s"${jsFile.getFileName}.map") sourceMapNameOpt.foreach { sourceMapName => val sourceMapFile = jsFile.resolveSibling(sourceMapName) linkerOutput = linkerOutput - .withSourceMap(PathOutputFile(sourceMapFile)) + .withSourceMap(PathOutputFile(sourceMapFile): @scala.annotation.nowarn("cat=deprecation")) .withSourceMapURI(java.net.URI.create(sourceMapFile.getFileName.toString)) } - linker.link(irFiles, moduleInitializers, linkerOutput, logger).map { + (linker.link(irFiles, moduleInitializers, linkerOutput, logger): @scala.annotation.nowarn("cat=deprecation")).map { file => Report( publicModules = Seq(Report.Module( diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala index 6575b532a9a..3425b9b230d 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala @@ -66,7 +66,7 @@ class ZincWorkerImpl( ) extends ZincWorkerApi with AutoCloseable { private val zincLogLevel = if (zincLogDebug) sbt.util.Level.Debug else sbt.util.Level.Info private[this] val ic = new sbt.internal.inc.IncrementalCompilerImpl() - private val javaOnlyCompilersCache = mutable.Map.empty[Seq[String], SoftReference[Compilers]] + private val javaOnlyCompilersCache = new ZincWorkerImpl.Cache[Seq[String], Compilers] private val filterJavacRuntimeOptions: String => Boolean = opt => opt.startsWith("-J") @@ -74,41 +74,37 @@ class ZincWorkerImpl( // Only options relevant for the compiler runtime influence the cached instance val javacRuntimeOptions = javacOptions.filter(filterJavacRuntimeOptions) - javaOnlyCompilersCache.get(javacRuntimeOptions) match { - case Some(SoftReference(compilers)) => compilers - case _ => - // Keep the classpath as written by the user - val classpathOptions = ClasspathOptions.of( - /*bootLibrary*/ false, - /*compiler*/ false, - /*extra*/ false, - /*autoBoot*/ false, - /*filterLibrary*/ false - ) + javaOnlyCompilersCache.getOrElseCreate(javacRuntimeOptions) { + // Keep the classpath as written by the user + val classpathOptions = ClasspathOptions.of( + /*bootLibrary*/ false, + /*compiler*/ false, + /*extra*/ false, + /*autoBoot*/ false, + /*filterLibrary*/ false + ) - val dummyFile = new java.io.File("") - // Zinc does not have an entry point for Java-only compilation, so we need - // to make up a dummy ScalaCompiler instance. - val scalac = ZincUtil.scalaCompiler( - new ScalaInstance( - version = "", - loader = null, - loaderCompilerOnly = null, - loaderLibraryOnly = null, - libraryJars = Array(dummyFile), - compilerJars = Array(dummyFile), - allJars = new Array(0), - explicitActual = Some("") - ), - dummyFile, - classpathOptions // this is used for javac too - ) + val dummyFile = new java.io.File("") + // Zinc does not have an entry point for Java-only compilation, so we need + // to make up a dummy ScalaCompiler instance. + val scalac = ZincUtil.scalaCompiler( + new ScalaInstance( + version = "", + loader = null, + loaderCompilerOnly = null, + loaderLibraryOnly = null, + libraryJars = Array(dummyFile), + compilerJars = Array(dummyFile), + allJars = new Array(0), + explicitActual = Some("") + ), + dummyFile, + classpathOptions // this is used for javac too + ) - val javaTools = getLocalOrCreateJavaTools(javacRuntimeOptions) + val javaTools = getLocalOrCreateJavaTools(javacRuntimeOptions) - val compilers = ic.compilers(javaTools, scalac) - javaOnlyCompilersCache.update(javacRuntimeOptions, SoftReference(compilers)) - compilers + ic.compilers(javaTools, scalac) } } @@ -407,26 +403,20 @@ class ZincWorkerImpl( // for now this just grows unbounded; YOLO // But at least we do not prevent unloading/garbage collecting of classloaders private[this] val classloaderCache = - collection.mutable.LinkedHashMap.empty[Long, SoftReference[ClassLoader]] + new ZincWorkerImpl.AutoCloseableCache[Long, ClassLoader] def getCachedClassLoader(compilersSig: Long, combinedCompilerJars: Array[java.io.File])(implicit ctx: ZincWorkerApi.Ctx ): ClassLoader = { - classloaderCache.synchronized { - classloaderCache.get(compilersSig) match { - case Some(SoftReference(cl)) => cl - case _ => - // the Scala compiler must load the `xsbti.*` classes from the same loader than `ZincWorkerImpl` - val sharedPrefixes = Seq("xsbti") - val cl = mill.api.ClassLoader.create( - combinedCompilerJars.map(_.toURI.toURL).toSeq, - parent = null, - sharedLoader = getClass.getClassLoader, - sharedPrefixes - ) - classloaderCache.update(compilersSig, SoftReference(cl)) - cl - } + classloaderCache.getOrElseCreate(compilersSig) { + // the Scala compiler must load the `xsbti.*` classes from the same loader than `ZincWorkerImpl` + val sharedPrefixes = Seq("xsbti") + mill.api.ClassLoader.create( + combinedCompilerJars.map(_.toURI.toURL).toSeq, + parent = null, + sharedLoader = getClass.getClassLoader, + sharedPrefixes + ) } } @@ -654,19 +644,6 @@ class ZincWorkerImpl( } override def close(): Unit = { - val closeableClassloaders = classloaderCache - .flatMap(_._2.get) - .collect { case v: AutoCloseable => v } - - val urlClassLoaders = - classloaderCache.flatMap(_._2.get).collect { case t: java.net.URLClassLoader => t } - - // Make sure we at least pick up all the URLClassLoaders, since we know those are - // AutoCloseable, although there may be other AutoCloseable classloaders as well - assert(urlClassLoaders.toSet[AutoCloseable].subsetOf(closeableClassloaders.toSet)) - - closeableClassloaders.foreach(_.close()) - classloaderCache.clear() javaOnlyCompilersCache.clear() } @@ -708,4 +685,36 @@ object ZincWorkerImpl { toAnalyze = List((List(start), deps(start).toList)) ).reverse } + + private class AutoCloseableCache[A, B <: AnyRef] extends Cache[A, B] { + override def clear(): Unit = { + import scala.jdk.CollectionConverters._ + + cache + .values() + .iterator() + .asScala + .foreach { case SoftReference(v: AutoCloseable) => + v.close() + } + + super.clear() + } + } + + private class Cache[A, B <: AnyRef] { + protected val cache = + new java.util.concurrent.ConcurrentHashMap[A, SoftReference[B]] + + def getOrElseCreate(a: A)(create: => B) = + cache.compute( + a, + { + case (_, v @ SoftReference(_)) => v + case _ => SoftReference(create) + } + )() + + def clear(): Unit = cache.clear() + } } From b77e6026d573f250bc5b3629297cda8960a1f42e Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 1 Oct 2024 14:36:11 +0200 Subject: [PATCH 2/4] Change order of classes and add scaladoc --- .../mill/scalalib/worker/ZincWorkerImpl.scala | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala index 3425b9b230d..7cd16ebd20f 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala @@ -686,22 +686,10 @@ object ZincWorkerImpl { ).reverse } - private class AutoCloseableCache[A, B <: AnyRef] extends Cache[A, B] { - override def clear(): Unit = { - import scala.jdk.CollectionConverters._ - - cache - .values() - .iterator() - .asScala - .foreach { case SoftReference(v: AutoCloseable) => - v.close() - } - - super.clear() - } - } - + /** + * In memory cache backed by a ConcurrentHashMap[A, SoftReference[B]] + * `SoftReference`s allow the GC to free values when memory is scarce + */ private class Cache[A, B <: AnyRef] { protected val cache = new java.util.concurrent.ConcurrentHashMap[A, SoftReference[B]] @@ -717,4 +705,21 @@ object ZincWorkerImpl { def clear(): Unit = cache.clear() } + + /** Cache that closes all `AutoClosable` values when `clear`ed */ + private class AutoCloseableCache[A, B <: AnyRef] extends Cache[A, B] { + override def clear(): Unit = { + import scala.jdk.CollectionConverters._ + + cache + .values() + .iterator() + .asScala + .foreach { case SoftReference(v: AutoCloseable) => + v.close() + } + + super.clear() + } + } } From 82ea1a82d78c60437df35b258844997b64595a67 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Tue, 1 Oct 2024 14:52:52 +0200 Subject: [PATCH 3/4] Run Scalafmt and scalafix --- .../scalajslib/worker/ScalaJSWorkerImpl.scala | 200 +++++++++--------- .../mill/scalalib/worker/ZincWorkerImpl.scala | 2 +- 2 files changed, 104 insertions(+), 98 deletions(-) diff --git a/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala b/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala index 06c7be62882..f9f18aaff8a 100644 --- a/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala +++ b/scalajslib/worker/1/src/mill/scalajslib/worker/ScalaJSWorkerImpl.scala @@ -46,122 +46,123 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi { private class Cache[A, B <: AnyRef] { protected val cache = new java.util.concurrent.ConcurrentHashMap[A, SoftReference[B]] - def getOrElseCreate(a: A)(create: => B) = + def getOrElseCreate(a: A)(create: => B): B = cache.compute( a, { case (_, v @ SoftReference(_)) => v - case _ => SoftReference(create) + case _ => SoftReference(create) } )() } private val irFileCache = StandardImpl.irFileCache() private val cache = new Cache[LinkerInput, (Linker, IRFileCache.Cache)] - def reuseOrCreate(input: LinkerInput): (Linker, IRFileCache.Cache) = cache.getOrElseCreate(input) { - val semantics = input.isFullLinkJS match { - case true => Semantics.Defaults.optimized - case false => Semantics.Defaults - } - val scalaJSModuleKind = input.moduleKind match { - case ModuleKind.NoModule => ScalaJSModuleKind.NoModule - case ModuleKind.CommonJSModule => ScalaJSModuleKind.CommonJSModule - case ModuleKind.ESModule => ScalaJSModuleKind.ESModule - } - @scala.annotation.nowarn("cat=deprecation") - def withESVersion_1_5_minus(esFeatures: ScalaJSESFeatures): ScalaJSESFeatures = { - val useECMAScript2015: Boolean = input.esFeatures.esVersion match { - case ESVersion.ES5_1 => false - case ESVersion.ES2015 => true - case v => throw new Exception( - s"ESVersion $v is not supported with Scala.js < 1.6. Either update Scala.js or use one of ESVersion.ES5_1 or ESVersion.ES2015" - ) + def reuseOrCreate(input: LinkerInput): (Linker, IRFileCache.Cache) = + cache.getOrElseCreate(input) { + val semantics = input.isFullLinkJS match { + case true => Semantics.Defaults.optimized + case false => Semantics.Defaults } - esFeatures.withUseECMAScript2015(useECMAScript2015) - } - def withESVersion_1_6_plus(esFeatures: ScalaJSESFeatures): ScalaJSESFeatures = { - val scalaJSESVersion: ScalaJSESVersion = input.esFeatures.esVersion match { - case ESVersion.ES5_1 => ScalaJSESVersion.ES5_1 - case ESVersion.ES2015 => ScalaJSESVersion.ES2015 - case ESVersion.ES2016 => ScalaJSESVersion.ES2016 - case ESVersion.ES2017 => ScalaJSESVersion.ES2017 - case ESVersion.ES2018 => ScalaJSESVersion.ES2018 - case ESVersion.ES2019 => ScalaJSESVersion.ES2019 - case ESVersion.ES2020 => ScalaJSESVersion.ES2020 - case ESVersion.ES2021 => ScalaJSESVersion.ES2021 + val scalaJSModuleKind = input.moduleKind match { + case ModuleKind.NoModule => ScalaJSModuleKind.NoModule + case ModuleKind.CommonJSModule => ScalaJSModuleKind.CommonJSModule + case ModuleKind.ESModule => ScalaJSModuleKind.ESModule } - esFeatures.withESVersion(scalaJSESVersion) - } - var scalaJSESFeatures: ScalaJSESFeatures = ScalaJSESFeatures.Defaults - .withAllowBigIntsForLongs(input.esFeatures.allowBigIntsForLongs) + @scala.annotation.nowarn("cat=deprecation") + def withESVersion_1_5_minus(esFeatures: ScalaJSESFeatures): ScalaJSESFeatures = { + val useECMAScript2015: Boolean = input.esFeatures.esVersion match { + case ESVersion.ES5_1 => false + case ESVersion.ES2015 => true + case v => throw new Exception( + s"ESVersion $v is not supported with Scala.js < 1.6. Either update Scala.js or use one of ESVersion.ES5_1 or ESVersion.ES2015" + ) + } + esFeatures.withUseECMAScript2015(useECMAScript2015) + } + def withESVersion_1_6_plus(esFeatures: ScalaJSESFeatures): ScalaJSESFeatures = { + val scalaJSESVersion: ScalaJSESVersion = input.esFeatures.esVersion match { + case ESVersion.ES5_1 => ScalaJSESVersion.ES5_1 + case ESVersion.ES2015 => ScalaJSESVersion.ES2015 + case ESVersion.ES2016 => ScalaJSESVersion.ES2016 + case ESVersion.ES2017 => ScalaJSESVersion.ES2017 + case ESVersion.ES2018 => ScalaJSESVersion.ES2018 + case ESVersion.ES2019 => ScalaJSESVersion.ES2019 + case ESVersion.ES2020 => ScalaJSESVersion.ES2020 + case ESVersion.ES2021 => ScalaJSESVersion.ES2021 + } + esFeatures.withESVersion(scalaJSESVersion) + } + var scalaJSESFeatures: ScalaJSESFeatures = ScalaJSESFeatures.Defaults + .withAllowBigIntsForLongs(input.esFeatures.allowBigIntsForLongs) - if (minorIsGreaterThanOrEqual(4)) { - scalaJSESFeatures = scalaJSESFeatures - .withAvoidClasses(input.esFeatures.avoidClasses) - .withAvoidLetsAndConsts(input.esFeatures.avoidLetsAndConsts) - } - scalaJSESFeatures = - if (minorIsGreaterThanOrEqual(6)) withESVersion_1_6_plus(scalaJSESFeatures) - else withESVersion_1_5_minus(scalaJSESFeatures) + if (minorIsGreaterThanOrEqual(4)) { + scalaJSESFeatures = scalaJSESFeatures + .withAvoidClasses(input.esFeatures.avoidClasses) + .withAvoidLetsAndConsts(input.esFeatures.avoidLetsAndConsts) + } + scalaJSESFeatures = + if (minorIsGreaterThanOrEqual(6)) withESVersion_1_6_plus(scalaJSESFeatures) + else withESVersion_1_5_minus(scalaJSESFeatures) - val useClosure = input.isFullLinkJS && input.moduleKind != ModuleKind.ESModule - val partialConfig = StandardConfig() - .withOptimizer(input.optimizer) - .withClosureCompilerIfAvailable(useClosure) - .withSemantics(semantics) - .withModuleKind(scalaJSModuleKind) - .withESFeatures(scalaJSESFeatures) - .withSourceMap(input.sourceMap) + val useClosure = input.isFullLinkJS && input.moduleKind != ModuleKind.ESModule + val partialConfig = StandardConfig() + .withOptimizer(input.optimizer) + .withClosureCompilerIfAvailable(useClosure) + .withSemantics(semantics) + .withModuleKind(scalaJSModuleKind) + .withESFeatures(scalaJSESFeatures) + .withSourceMap(input.sourceMap) - def withModuleSplitStyle_1_3_plus(config: StandardConfig): StandardConfig = { - config.withModuleSplitStyle( + def withModuleSplitStyle_1_3_plus(config: StandardConfig): StandardConfig = { + config.withModuleSplitStyle( + input.moduleSplitStyle match { + case ModuleSplitStyle.FewestModules => ScalaJSModuleSplitStyle.FewestModules() + case ModuleSplitStyle.SmallestModules => ScalaJSModuleSplitStyle.SmallestModules() + case v @ ModuleSplitStyle.SmallModulesFor(packages) => + if (minorIsGreaterThanOrEqual(10)) ScalaJSModuleSplitStyle.SmallModulesFor(packages) + else throw new Exception( + s"ModuleSplitStyle $v is not supported with Scala.js < 1.10. Either update Scala.js or use one of ModuleSplitStyle.SmallestModules or ModuleSplitStyle.FewestModules" + ) + } + ) + } + + def withModuleSplitStyle_1_2_minus(config: StandardConfig): StandardConfig = { input.moduleSplitStyle match { - case ModuleSplitStyle.FewestModules => ScalaJSModuleSplitStyle.FewestModules() - case ModuleSplitStyle.SmallestModules => ScalaJSModuleSplitStyle.SmallestModules() - case v @ ModuleSplitStyle.SmallModulesFor(packages) => - if (minorIsGreaterThanOrEqual(10)) ScalaJSModuleSplitStyle.SmallModulesFor(packages) - else throw new Exception( - s"ModuleSplitStyle $v is not supported with Scala.js < 1.10. Either update Scala.js or use one of ModuleSplitStyle.SmallestModules or ModuleSplitStyle.FewestModules" + case ModuleSplitStyle.FewestModules => + case v => throw new Exception( + s"ModuleSplitStyle $v is not supported with Scala.js < 1.2. Either update Scala.js or use ModuleSplitStyle.FewestModules" ) } - ) - } - - def withModuleSplitStyle_1_2_minus(config: StandardConfig): StandardConfig = { - input.moduleSplitStyle match { - case ModuleSplitStyle.FewestModules => - case v => throw new Exception( - s"ModuleSplitStyle $v is not supported with Scala.js < 1.2. Either update Scala.js or use ModuleSplitStyle.FewestModules" - ) + config } - config - } - val withModuleSplitStyle = - if (minorIsGreaterThanOrEqual(3)) withModuleSplitStyle_1_3_plus(partialConfig) - else withModuleSplitStyle_1_2_minus(partialConfig) + val withModuleSplitStyle = + if (minorIsGreaterThanOrEqual(3)) withModuleSplitStyle_1_3_plus(partialConfig) + else withModuleSplitStyle_1_2_minus(partialConfig) - val withOutputPatterns = - if (minorIsGreaterThanOrEqual(3)) - withModuleSplitStyle - .withOutputPatterns( - ScalaJSOutputPatterns.Defaults - .withJSFile(input.outputPatterns.jsFile) - .withJSFileURI(input.outputPatterns.jsFileURI) - .withModuleName(input.outputPatterns.moduleName) - .withSourceMapFile(input.outputPatterns.sourceMapFile) - .withSourceMapURI(input.outputPatterns.sourceMapURI) - ) - else withModuleSplitStyle + val withOutputPatterns = + if (minorIsGreaterThanOrEqual(3)) + withModuleSplitStyle + .withOutputPatterns( + ScalaJSOutputPatterns.Defaults + .withJSFile(input.outputPatterns.jsFile) + .withJSFileURI(input.outputPatterns.jsFileURI) + .withModuleName(input.outputPatterns.moduleName) + .withSourceMapFile(input.outputPatterns.sourceMapFile) + .withSourceMapURI(input.outputPatterns.sourceMapURI) + ) + else withModuleSplitStyle - val withMinify = - if (minorIsGreaterThanOrEqual(16)) withOutputPatterns.withMinify(input.minify) - else withOutputPatterns + val withMinify = + if (minorIsGreaterThanOrEqual(16)) withOutputPatterns.withMinify(input.minify) + else withOutputPatterns - val linker = StandardImpl.clearableLinker(withMinify) - val irFileCacheCache = irFileCache.newCache - (linker, irFileCacheCache) - } + val linker = StandardImpl.clearableLinker(withMinify) + val irFileCacheCache = irFileCache.newCache + (linker, irFileCacheCache) + } } private val logger = new Logger { def log(level: Level, message: => String): Unit = { @@ -239,16 +240,21 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi { if (useLegacy) { val jsFileName = "out.js" val jsFile = new File(dest, jsFileName).toPath() - var linkerOutput = (LinkerOutput(PathOutputFile(jsFile)): @scala.annotation.nowarn("cat=deprecation")) - .withJSFileURI(java.net.URI.create(jsFile.getFileName.toString)) + var linkerOutput = + (LinkerOutput(PathOutputFile(jsFile)): @scala.annotation.nowarn("cat=deprecation")) + .withJSFileURI(java.net.URI.create(jsFile.getFileName.toString)) val sourceMapNameOpt = Option.when(sourceMap)(s"${jsFile.getFileName}.map") sourceMapNameOpt.foreach { sourceMapName => val sourceMapFile = jsFile.resolveSibling(sourceMapName) linkerOutput = linkerOutput - .withSourceMap(PathOutputFile(sourceMapFile): @scala.annotation.nowarn("cat=deprecation")) + .withSourceMap( + PathOutputFile(sourceMapFile): @scala.annotation.nowarn("cat=deprecation") + ) .withSourceMapURI(java.net.URI.create(sourceMapFile.getFileName.toString)) } - (linker.link(irFiles, moduleInitializers, linkerOutput, logger): @scala.annotation.nowarn("cat=deprecation")).map { + (linker.link(irFiles, moduleInitializers, linkerOutput, logger): @scala.annotation.nowarn( + "cat=deprecation" + )).map { file => Report( publicModules = Seq(Report.Module( diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala index 7cd16ebd20f..2bdc9290d09 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala @@ -694,7 +694,7 @@ object ZincWorkerImpl { protected val cache = new java.util.concurrent.ConcurrentHashMap[A, SoftReference[B]] - def getOrElseCreate(a: A)(create: => B) = + def getOrElseCreate(a: A)(create: => B): B = cache.compute( a, { From ac7e43845ac5a501e0c806fda69682859ee590c9 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele Date: Wed, 2 Oct 2024 07:09:33 +0200 Subject: [PATCH 4/4] Add closeAutoCloseables paramenter instead of subclassing --- .../mill/scalalib/worker/ZincWorkerImpl.scala | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala index 2bdc9290d09..56ad5531c9d 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala @@ -402,8 +402,7 @@ class ZincWorkerImpl( // for now this just grows unbounded; YOLO // But at least we do not prevent unloading/garbage collecting of classloaders - private[this] val classloaderCache = - new ZincWorkerImpl.AutoCloseableCache[Long, ClassLoader] + private[this] val classloaderCache = new ZincWorkerImpl.Cache[Long, ClassLoader] def getCachedClassLoader(compilersSig: Long, combinedCompilerJars: Array[java.io.File])(implicit ctx: ZincWorkerApi.Ctx @@ -644,7 +643,7 @@ class ZincWorkerImpl( } override def close(): Unit = { - classloaderCache.clear() + classloaderCache.clear(closeAutoCloseables = true) javaOnlyCompilersCache.clear() } } @@ -703,23 +702,20 @@ object ZincWorkerImpl { } )() - def clear(): Unit = cache.clear() - } + import scala.jdk.CollectionConverters._ + def clear(closeAutoCloseables: Boolean): Unit = { - /** Cache that closes all `AutoClosable` values when `clear`ed */ - private class AutoCloseableCache[A, B <: AnyRef] extends Cache[A, B] { - override def clear(): Unit = { - import scala.jdk.CollectionConverters._ - - cache - .values() - .iterator() - .asScala - .foreach { case SoftReference(v: AutoCloseable) => - v.close() - } + if (closeAutoCloseables) { + cache + .values() + .iterator() + .asScala + .foreach { case SoftReference(v: AutoCloseable) => + v.close() + } + } - super.clear() + cache.clear() } } }