From 4d84a1f2e7ed9f6dd41e5fafd884fe46313d824a Mon Sep 17 00:00:00 2001 From: Adam Fraser Date: Thu, 7 Sep 2023 20:27:02 -0700 Subject: [PATCH 1/3] bounded thread pool scheduler --- .../scala/zio/internal/DefaultExecutors.scala | 5 +- .../main/scala/zio/internal/ZScheduler.scala | 84 ++++++++++--------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala b/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala index 3361408d3555..a169a0244b95 100644 --- a/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala +++ b/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala @@ -24,7 +24,10 @@ import java.util.concurrent.{RejectedExecutionException, ThreadPoolExecutor} private[zio] abstract class DefaultExecutors { final def makeDefault(): zio.Executor = - new ZScheduler + new ZScheduler(true) + + final def makeDefault(autoBlocking: Boolean): zio.Executor = + new ZScheduler(autoBlocking) final def fromThreadPoolExecutor( es: ThreadPoolExecutor diff --git a/core/jvm/src/main/scala/zio/internal/ZScheduler.scala b/core/jvm/src/main/scala/zio/internal/ZScheduler.scala index 66c4cc463ff9..9b4bc8f7c89b 100644 --- a/core/jvm/src/main/scala/zio/internal/ZScheduler.scala +++ b/core/jvm/src/main/scala/zio/internal/ZScheduler.scala @@ -28,7 +28,7 @@ import java.util.concurrent.locks.LockSupport * applications. Inspired by "Making the Tokio Scheduler 10X Faster" by Carl * Lerche. [[https://tokio.rs/blog/2019-10-scheduler]] */ -private final class ZScheduler extends Executor { +private final class ZScheduler(autoBlocking: Boolean) extends Executor { private[this] val poolSize = java.lang.Runtime.getRuntime.availableProcessors private[this] val cache = MutableConcurrentQueue.unbounded[ZScheduler.Worker] private[this] val globalQueue = MutableConcurrentQueue.unbounded[Runnable] @@ -47,7 +47,7 @@ private final class ZScheduler extends Executor { } workers.foreach(_.start()) - private[this] val supervisor = makeSupervisor() + private[this] val supervisor = makeSupervisor(autoBlocking) supervisor.setName("ZScheduler-Supervisor") supervisor.setDaemon(true) supervisor.start() @@ -257,58 +257,60 @@ private final class ZScheduler extends Executor { } } - private[this] def makeSupervisor(): ZScheduler.Supervisor = + private[this] def makeSupervisor(autoBlocking: Boolean): ZScheduler.Supervisor = new ZScheduler.Supervisor { override def run(): Unit = { var currentTime = java.lang.System.currentTimeMillis() val identifiedLocations = makeLocations() val previousOpCounts = Array.fill(poolSize)(-1L) while (!isInterrupted) { - var workerId = 0 - while (workerId != poolSize) { - val currentWorker = workers(workerId) - if (currentWorker.active) { - val currentOpCount = currentWorker.opCount - val previousOpCount = previousOpCounts(workerId) - if (currentOpCount == previousOpCount) { - val currentRunnable = currentWorker.currentRunnable - if (currentRunnable.isInstanceOf[FiberRunnable]) { - val fiberRunnable = currentRunnable.asInstanceOf[FiberRunnable] - val location = fiberRunnable.location - if (location ne Trace.empty) { - val identifiedCount = identifiedLocations.put(location) - val submittedCount = submittedLocations.get(location) - if (submittedCount > 64 && identifiedCount >= submittedCount / 2) { - blockingLocations += location + if (autoBlocking) { + var workerId = 0 + while (workerId != poolSize) { + val currentWorker = workers(workerId) + if (currentWorker.active) { + val currentOpCount = currentWorker.opCount + val previousOpCount = previousOpCounts(workerId) + if (currentOpCount == previousOpCount) { + val currentRunnable = currentWorker.currentRunnable + if (currentRunnable.isInstanceOf[FiberRunnable]) { + val fiberRunnable = currentRunnable.asInstanceOf[FiberRunnable] + val location = fiberRunnable.location + if (location ne Trace.empty) { + val identifiedCount = identifiedLocations.put(location) + val submittedCount = submittedLocations.get(location) + if (submittedCount > 64 && identifiedCount >= submittedCount / 2) { + blockingLocations += location + } } } - } - previousOpCounts(workerId) = -1L - currentWorker.blocking = true - val runnables = currentWorker.localQueue.pollUpTo(256) - globalQueue.offerAll(runnables) - val worker = cache.poll(null) - if (worker eq null) { - val worker = makeWorker() - worker.setName(s"ZScheduler-Worker-$workerId") - worker.setDaemon(true) - workers(workerId) = worker - worker.start() + previousOpCounts(workerId) = -1L + currentWorker.blocking = true + val runnables = currentWorker.localQueue.pollUpTo(256) + globalQueue.offerAll(runnables) + val worker = cache.poll(null) + if (worker eq null) { + val worker = makeWorker() + worker.setName(s"ZScheduler-Worker-$workerId") + worker.setDaemon(true) + workers(workerId) = worker + worker.start() + } else { + state.getAndIncrement() + worker.setName(s"ZScheduler-Worker-$workerId") + workers(workerId) = worker + worker.blocking = false + worker.active = true + LockSupport.unpark(worker) + } } else { - state.getAndIncrement() - worker.setName(s"ZScheduler-Worker-$workerId") - workers(workerId) = worker - worker.blocking = false - worker.active = true - LockSupport.unpark(worker) + previousOpCounts(workerId) = currentOpCount } } else { - previousOpCounts(workerId) = currentOpCount + previousOpCounts(workerId) = -1L } - } else { - previousOpCounts(workerId) = -1L + workerId += 1 } - workerId += 1 } val deadline = currentTime + 100 var loop = true From ade9e1c1e9ec8d632c09ed954dbe50abe3012592 Mon Sep 17 00:00:00 2001 From: Adam Fraser Date: Thu, 7 Sep 2023 23:34:14 -0700 Subject: [PATCH 2/3] optimize --- .../main/scala/zio/internal/ZScheduler.scala | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/core/jvm/src/main/scala/zio/internal/ZScheduler.scala b/core/jvm/src/main/scala/zio/internal/ZScheduler.scala index 9b4bc8f7c89b..af21be188133 100644 --- a/core/jvm/src/main/scala/zio/internal/ZScheduler.scala +++ b/core/jvm/src/main/scala/zio/internal/ZScheduler.scala @@ -47,10 +47,12 @@ private final class ZScheduler(autoBlocking: Boolean) extends Executor { } workers.foreach(_.start()) - private[this] val supervisor = makeSupervisor(autoBlocking) - supervisor.setName("ZScheduler-Supervisor") - supervisor.setDaemon(true) - supervisor.start() + if (autoBlocking) { + val supervisor = makeSupervisor() + supervisor.setName("ZScheduler-Supervisor") + supervisor.setDaemon(true) + supervisor.start() + } def metrics(implicit unsafe: Unsafe): Option[ExecutionMetrics] = { val metrics = new ExecutionMetrics { @@ -257,60 +259,58 @@ private final class ZScheduler(autoBlocking: Boolean) extends Executor { } } - private[this] def makeSupervisor(autoBlocking: Boolean): ZScheduler.Supervisor = + private[this] def makeSupervisor(): ZScheduler.Supervisor = new ZScheduler.Supervisor { override def run(): Unit = { var currentTime = java.lang.System.currentTimeMillis() val identifiedLocations = makeLocations() val previousOpCounts = Array.fill(poolSize)(-1L) while (!isInterrupted) { - if (autoBlocking) { - var workerId = 0 - while (workerId != poolSize) { - val currentWorker = workers(workerId) - if (currentWorker.active) { - val currentOpCount = currentWorker.opCount - val previousOpCount = previousOpCounts(workerId) - if (currentOpCount == previousOpCount) { - val currentRunnable = currentWorker.currentRunnable - if (currentRunnable.isInstanceOf[FiberRunnable]) { - val fiberRunnable = currentRunnable.asInstanceOf[FiberRunnable] - val location = fiberRunnable.location - if (location ne Trace.empty) { - val identifiedCount = identifiedLocations.put(location) - val submittedCount = submittedLocations.get(location) - if (submittedCount > 64 && identifiedCount >= submittedCount / 2) { - blockingLocations += location - } + var workerId = 0 + while (workerId != poolSize) { + val currentWorker = workers(workerId) + if (currentWorker.active) { + val currentOpCount = currentWorker.opCount + val previousOpCount = previousOpCounts(workerId) + if (currentOpCount == previousOpCount) { + val currentRunnable = currentWorker.currentRunnable + if (currentRunnable.isInstanceOf[FiberRunnable]) { + val fiberRunnable = currentRunnable.asInstanceOf[FiberRunnable] + val location = fiberRunnable.location + if (location ne Trace.empty) { + val identifiedCount = identifiedLocations.put(location) + val submittedCount = submittedLocations.get(location) + if (submittedCount > 64 && identifiedCount >= submittedCount / 2) { + blockingLocations += location } } - previousOpCounts(workerId) = -1L - currentWorker.blocking = true - val runnables = currentWorker.localQueue.pollUpTo(256) - globalQueue.offerAll(runnables) - val worker = cache.poll(null) - if (worker eq null) { - val worker = makeWorker() - worker.setName(s"ZScheduler-Worker-$workerId") - worker.setDaemon(true) - workers(workerId) = worker - worker.start() - } else { - state.getAndIncrement() - worker.setName(s"ZScheduler-Worker-$workerId") - workers(workerId) = worker - worker.blocking = false - worker.active = true - LockSupport.unpark(worker) - } + } + previousOpCounts(workerId) = -1L + currentWorker.blocking = true + val runnables = currentWorker.localQueue.pollUpTo(256) + globalQueue.offerAll(runnables) + val worker = cache.poll(null) + if (worker eq null) { + val worker = makeWorker() + worker.setName(s"ZScheduler-Worker-$workerId") + worker.setDaemon(true) + workers(workerId) = worker + worker.start() } else { - previousOpCounts(workerId) = currentOpCount + state.getAndIncrement() + worker.setName(s"ZScheduler-Worker-$workerId") + workers(workerId) = worker + worker.blocking = false + worker.active = true + LockSupport.unpark(worker) } } else { - previousOpCounts(workerId) = -1L + previousOpCounts(workerId) = currentOpCount } - workerId += 1 + } else { + previousOpCounts(workerId) = -1L } + workerId += 1 } val deadline = currentTime + 100 var loop = true From 57dc61c6c675f1c4c80f2d177bb5b4b37e878d5d Mon Sep 17 00:00:00 2001 From: Adam Fraser Date: Fri, 8 Sep 2023 00:00:39 -0700 Subject: [PATCH 3/3] cleanup --- core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala b/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala index a169a0244b95..cded50ce2dbb 100644 --- a/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala +++ b/core/jvm/src/main/scala/zio/internal/DefaultExecutors.scala @@ -24,7 +24,7 @@ import java.util.concurrent.{RejectedExecutionException, ThreadPoolExecutor} private[zio] abstract class DefaultExecutors { final def makeDefault(): zio.Executor = - new ZScheduler(true) + makeDefault(true) final def makeDefault(autoBlocking: Boolean): zio.Executor = new ZScheduler(autoBlocking)