Skip to content

Commit e85906b

Browse files
committed
Limit parallel Scala.js linking jobs to avoid high memory pressure
Fix: #6226 I just hardcoded the limit to `2`.
1 parent ef68019 commit e85906b

File tree

3 files changed

+78
-5
lines changed

3 files changed

+78
-5
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package mill.scalajslib.worker
2+
3+
import java.util.concurrent.Semaphore
4+
5+
/**
6+
* Limit the parallelism of jobs run via [[runLimited]].
7+
* @param maxJobs The maximal parallelism
8+
*/
9+
class ParallelismLimiter(maxJobs: Int) {
10+
11+
private val linkerJobsSemaphore = Semaphore(maxJobs)
12+
13+
def runLimited[T](thunk: => T): T = {
14+
linkerJobsSemaphore.acquire()
15+
try {
16+
thunk
17+
} finally {
18+
linkerJobsSemaphore.release()
19+
}
20+
}
21+
22+
}

libs/scalajslib/src/mill/scalajslib/worker/ScalaJSWorker.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package mill.scalajslib.worker
22

33
import mill.*
44
import mill.scalajslib.api
5-
import mill.scalajslib.worker.{api => workerApi}
5+
import mill.scalajslib.worker.api as workerApi
66
import mill.api.TaskCtx
77
import mill.api.Result
88
import mill.api.daemon.internal.internal
@@ -11,9 +11,10 @@ import mill.util.CachedFactory
1111

1212
import java.io.File
1313
import java.net.URLClassLoader
14+
import java.util.concurrent.Semaphore
1415

1516
@internal
16-
private[scalajslib] class ScalaJSWorker(jobs: Int)
17+
private[scalajslib] class ScalaJSWorker(jobs: Int, linkerJobs: Int)
1718
extends CachedFactory[Seq[mill.PathRef], (URLClassLoader, workerApi.ScalaJSWorkerApi)] {
1819

1920
override def setup(key: Seq[PathRef]) = {
@@ -190,6 +191,8 @@ private[scalajslib] class ScalaJSWorker(jobs: Int)
190191
}
191192
}
192193

194+
private val linkerJobLimiter = ParallelismLimiter(linkerJobs)
195+
193196
def link(
194197
toolsClasspath: Seq[mill.PathRef],
195198
runClasspath: Seq[mill.PathRef],
@@ -207,7 +210,7 @@ private[scalajslib] class ScalaJSWorker(jobs: Int)
207210
minify: Boolean,
208211
importMap: Seq[api.ESModuleImportMapping],
209212
experimentalUseWebAssembly: Boolean
210-
): Result[api.Report] = {
213+
): Result[api.Report] = linkerJobLimiter.runLimited {
211214
withValue(toolsClasspath) { case (_, bridge) =>
212215
bridge.link(
213216
runClasspath = runClasspath.iterator.map(_.path.toNIO).toSeq,
@@ -258,7 +261,11 @@ private[scalajslib] class ScalaJSWorker(jobs: Int)
258261
@internal
259262
private[scalajslib] object ScalaJSWorkerExternalModule extends mill.api.ExternalModule {
260263

261-
def scalaJSWorker: Worker[ScalaJSWorker] =
262-
Task.Worker { new ScalaJSWorker(Task.ctx().jobs) }
264+
def scalaJSWorker: Worker[ScalaJSWorker] = Task.Worker {
265+
new ScalaJSWorker(
266+
jobs = Task.ctx().jobs,
267+
linkerJobs = 2
268+
)
269+
}
263270
lazy val millDiscover = Discover[this.type]
264271
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package mill.scalajslib.worker
2+
3+
import utest.*
4+
5+
import java.util.concurrent.atomic.AtomicInteger
6+
7+
class ParallelismLimiterTests extends TestSuite {
8+
9+
override def tests = Tests {
10+
test("limitedoJobs") {
11+
12+
val maxJobs = 3
13+
val limiter = ParallelismLimiter(maxJobs)
14+
15+
val concurrentCount = new AtomicInteger(0)
16+
val maxObserved = new AtomicInteger(0)
17+
18+
def work(i: Int, workTimeMs: Int): Unit = {
19+
val before = concurrentCount.incrementAndGet()
20+
maxObserved.updateAndGet(v => Math.max(v, before))
21+
22+
Thread.sleep(workTimeMs)
23+
24+
val after = concurrentCount.decrementAndGet()
25+
assert(after >= 0)
26+
}
27+
28+
val tasks = (1 to 10).map { i =>
29+
new Thread(() =>
30+
limiter.runLimited {
31+
work(i, 50)
32+
}
33+
)
34+
}
35+
36+
tasks.foreach(_.start())
37+
tasks.foreach(_.join())
38+
39+
assert(maxObserved.get() <= maxJobs)
40+
}
41+
42+
}
43+
44+
}

0 commit comments

Comments
 (0)