From eecbd293e719208dae6b091cd6cd2d7a76b2a2cd Mon Sep 17 00:00:00 2001 From: Paul J Thordarson Date: Sat, 5 Apr 2025 20:23:46 -0400 Subject: [PATCH 1/5] Create refactoring heirarchy --- .../js/src/main/scala/cats/effect/IOApp.scala | 2 +- .../scala/cats/effect/IOAppPlatform.scala | 19 +++++++++++++++++++ .../cats/effect/IOAppMultiThreaded.scala | 19 +++++++++++++++++++ .../src/main/scala/cats/effect/IOApp.scala | 2 +- .../scala/cats/effect/IOAppPlatform.scala | 19 +++++++++++++++++++ .../src/main/scala/cats/effect/IOApp.scala | 2 +- .../scala/cats/effect/IOAppPlatform.scala | 19 +++++++++++++++++++ .../main/scala/cats/effect/IOAppCommon.scala | 19 +++++++++++++++++++ 8 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 core/js/src/main/scala/cats/effect/IOAppPlatform.scala create mode 100644 core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala create mode 100644 core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala create mode 100644 core/native/src/main/scala/cats/effect/IOAppPlatform.scala create mode 100644 core/shared/src/main/scala/cats/effect/IOAppCommon.scala diff --git a/core/js/src/main/scala/cats/effect/IOApp.scala b/core/js/src/main/scala/cats/effect/IOApp.scala index 878d40281e..94ebe78399 100644 --- a/core/js/src/main/scala/cats/effect/IOApp.scala +++ b/core/js/src/main/scala/cats/effect/IOApp.scala @@ -143,7 +143,7 @@ import scala.util.Try * @see * [[IOApp.Simple]] */ -trait IOApp { +trait IOApp extends IOAppPlatform { private[this] var _runtime: unsafe.IORuntime = null diff --git a/core/js/src/main/scala/cats/effect/IOAppPlatform.scala b/core/js/src/main/scala/cats/effect/IOAppPlatform.scala new file mode 100644 index 0000000000..6b607a7ea2 --- /dev/null +++ b/core/js/src/main/scala/cats/effect/IOAppPlatform.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2020-2025 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +trait IOAppPlatform extends IOAppCommon {} diff --git a/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala b/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala new file mode 100644 index 0000000000..1e2aacb6b0 --- /dev/null +++ b/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2020-2025 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +trait IOAppMultiThreaded {} diff --git a/core/jvm/src/main/scala/cats/effect/IOApp.scala b/core/jvm/src/main/scala/cats/effect/IOApp.scala index 0ae9bc9df5..cb78c97e63 100644 --- a/core/jvm/src/main/scala/cats/effect/IOApp.scala +++ b/core/jvm/src/main/scala/cats/effect/IOApp.scala @@ -140,7 +140,7 @@ import java.util.concurrent.atomic.AtomicInteger * @see * [[IOApp.Simple]] */ -trait IOApp { +trait IOApp extends IOAppPlatform { private[this] var _runtime: unsafe.IORuntime = null diff --git a/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala b/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala new file mode 100644 index 0000000000..3089072c4b --- /dev/null +++ b/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2020-2025 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded {} diff --git a/core/native/src/main/scala/cats/effect/IOApp.scala b/core/native/src/main/scala/cats/effect/IOApp.scala index 293db2207c..772e539184 100644 --- a/core/native/src/main/scala/cats/effect/IOApp.scala +++ b/core/native/src/main/scala/cats/effect/IOApp.scala @@ -139,7 +139,7 @@ import java.util.concurrent.atomic.AtomicInteger * @see * [[IOApp.Simple]] */ -trait IOApp { +trait IOApp extends IOAppPlatform { private[this] var _runtime: unsafe.IORuntime = null diff --git a/core/native/src/main/scala/cats/effect/IOAppPlatform.scala b/core/native/src/main/scala/cats/effect/IOAppPlatform.scala new file mode 100644 index 0000000000..3089072c4b --- /dev/null +++ b/core/native/src/main/scala/cats/effect/IOAppPlatform.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2020-2025 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded {} diff --git a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala new file mode 100644 index 0000000000..34d741c0dd --- /dev/null +++ b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2020-2025 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect + +trait IOAppCommon {} From ec7bc90a3a6db7f4ba129a5c65f74020116c4b11 Mon Sep 17 00:00:00 2001 From: Paul J Thordarson Date: Mon, 5 May 2025 11:16:17 -0400 Subject: [PATCH 2/5] Move runtime creation. --- .../js/src/main/scala/cats/effect/IOApp.scala | 12 +------ .../scala/cats/effect/IOAppPlatform.scala | 18 +++++++++- .../src/main/scala/cats/effect/IOApp.scala | 32 ++--------------- .../scala/cats/effect/IOAppPlatform.scala | 36 ++++++++++++++++++- .../src/main/scala/cats/effect/IOApp.scala | 29 +-------------- .../scala/cats/effect/IOAppPlatform.scala | 35 +++++++++++++++++- .../main/scala/cats/effect/IOAppCommon.scala | 6 +++- 7 files changed, 95 insertions(+), 73 deletions(-) diff --git a/core/js/src/main/scala/cats/effect/IOApp.scala b/core/js/src/main/scala/cats/effect/IOApp.scala index 94ebe78399..e97c548ddc 100644 --- a/core/js/src/main/scala/cats/effect/IOApp.scala +++ b/core/js/src/main/scala/cats/effect/IOApp.scala @@ -205,17 +205,7 @@ trait IOApp extends IOAppPlatform { val installed = if (runtime == null) { import unsafe.IORuntime - val installed = IORuntime installGlobal { - val compute = IORuntime.createBatchingMacrotaskExecutor(reportFailure = t => - reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime)) - - IORuntime( - compute, - compute, - IORuntime.defaultScheduler, - () => IORuntime.resetGlobal(), - runtimeConfig) - } + val installed = IORuntime installGlobal defaultGlobalRuntime _runtime = IORuntime.global diff --git a/core/js/src/main/scala/cats/effect/IOAppPlatform.scala b/core/js/src/main/scala/cats/effect/IOAppPlatform.scala index 6b607a7ea2..20551b1ab7 100644 --- a/core/js/src/main/scala/cats/effect/IOAppPlatform.scala +++ b/core/js/src/main/scala/cats/effect/IOAppPlatform.scala @@ -16,4 +16,20 @@ package cats.effect -trait IOAppPlatform extends IOAppCommon {} +import cats.effect.unsafe.IORuntime + +trait IOAppPlatform extends IOAppCommon { + this: IOApp => + + private[effect] def defaultGlobalRuntime: IORuntime = { + val compute = IORuntime.createBatchingMacrotaskExecutor(reportFailure = t => + reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime)) + + IORuntime( + compute, + compute, + IORuntime.defaultScheduler, + () => IORuntime.resetGlobal(), + runtimeConfig) + } +} diff --git a/core/jvm/src/main/scala/cats/effect/IOApp.scala b/core/jvm/src/main/scala/cats/effect/IOApp.scala index cb78c97e63..58ed3ffff0 100644 --- a/core/jvm/src/main/scala/cats/effect/IOApp.scala +++ b/core/jvm/src/main/scala/cats/effect/IOApp.scala @@ -201,7 +201,7 @@ trait IOApp extends IOAppPlatform { // arbitrary constant is arbitrary private[this] lazy val queue = new ArrayBlockingQueue[AnyRef](32) - private[this] def handleTerminalFailure(t: Throwable): Unit = { + private[effect] def handleTerminalFailure(t: Throwable): Unit = { queue.clear() queue.put(t) } @@ -398,35 +398,7 @@ trait IOApp extends IOAppPlatform { val installed = if (runtime == null) { import unsafe.IORuntime - val installed = IORuntime installGlobal { - val (compute, poller, compDown) = - IORuntime.createWorkStealingComputeThreadPool( - threads = computeWorkerThreadCount, - reportFailure = t => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime), - blockedThreadDetectionEnabled = blockedThreadDetectionEnabled, - pollingSystem = pollingSystem, - uncaughtExceptionHandler = (_, t) => handleTerminalFailure(t) - ) - - val (blocking, blockDown) = - IORuntime.createDefaultBlockingExecutionContext( - threadPrefix = "io-blocking", - reportFailure = - (t: Throwable) => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) - ) - - IORuntime( - compute, - blocking, - compute, - List(poller), - { () => - compDown() - blockDown() - IORuntime.resetGlobal() - }, - runtimeConfig) - } + val installed = IORuntime installGlobal defaultGlobalRuntime _runtime = IORuntime.global diff --git a/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala b/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala index 3089072c4b..91d40814a8 100644 --- a/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala @@ -16,4 +16,38 @@ package cats.effect -trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded {} +import cats.effect.unsafe.IORuntime + +trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded { + this: IOApp => + + private[effect] def defaultGlobalRuntime: IORuntime = { + val (compute, poller, compDown) = + IORuntime.createWorkStealingComputeThreadPool( + threads = computeWorkerThreadCount, + reportFailure = t => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime), + blockedThreadDetectionEnabled = blockedThreadDetectionEnabled, + pollingSystem = pollingSystem, + uncaughtExceptionHandler = (_, t) => handleTerminalFailure(t) + ) + + val (blocking, blockDown) = + IORuntime.createDefaultBlockingExecutionContext( + threadPrefix = "io-blocking", + reportFailure = + (t: Throwable) => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) + ) + + IORuntime( + compute, + blocking, + compute, + List(poller), + { () => + compDown() + blockDown() + IORuntime.resetGlobal() + }, + runtimeConfig) + } +} diff --git a/core/native/src/main/scala/cats/effect/IOApp.scala b/core/native/src/main/scala/cats/effect/IOApp.scala index 772e539184..35c1500f4a 100644 --- a/core/native/src/main/scala/cats/effect/IOApp.scala +++ b/core/native/src/main/scala/cats/effect/IOApp.scala @@ -289,34 +289,7 @@ trait IOApp extends IOAppPlatform { val installed = if (runtime == null) { import unsafe.IORuntime - val installed = IORuntime installGlobal { - val (compute, poller, compDown) = - IORuntime.createWorkStealingComputeThreadPool( - threads = computeWorkerThreadCount, - reportFailure = t => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime), - blockedThreadDetectionEnabled = false, // TODO - pollingSystem = pollingSystem - ) - - val (blocking, blockDown) = - IORuntime.createDefaultBlockingExecutionContext( - threadPrefix = "io-blocking", - reportFailure = - (t: Throwable) => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) - ) - - IORuntime( - compute, - blocking, - compute, - List(poller), - { () => - compDown() - blockDown() - IORuntime.resetGlobal() - }, - runtimeConfig) - } + val installed = IORuntime installGlobal defaultGlobalRuntime _runtime = IORuntime.global diff --git a/core/native/src/main/scala/cats/effect/IOAppPlatform.scala b/core/native/src/main/scala/cats/effect/IOAppPlatform.scala index 3089072c4b..d7e207a8ba 100644 --- a/core/native/src/main/scala/cats/effect/IOAppPlatform.scala +++ b/core/native/src/main/scala/cats/effect/IOAppPlatform.scala @@ -16,4 +16,37 @@ package cats.effect -trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded {} +import cats.effect.unsafe.IORuntime + +trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded { + this: IOApp => + + private[effect] def defaultGlobalRuntime: IORuntime = { + val (compute, poller, compDown) = + IORuntime.createWorkStealingComputeThreadPool( + threads = computeWorkerThreadCount, + reportFailure = t => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime), + blockedThreadDetectionEnabled = false, // TODO + pollingSystem = pollingSystem + ) + + val (blocking, blockDown) = + IORuntime.createDefaultBlockingExecutionContext( + threadPrefix = "io-blocking", + reportFailure = + (t: Throwable) => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) + ) + + IORuntime( + compute, + blocking, + compute, + List(poller), + { () => + compDown() + blockDown() + IORuntime.resetGlobal() + }, + runtimeConfig) + } +} diff --git a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala index 34d741c0dd..c23df9219a 100644 --- a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala +++ b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala @@ -16,4 +16,8 @@ package cats.effect -trait IOAppCommon {} +import cats.effect.unsafe.IORuntime + +trait IOAppCommon { + private[effect] def defaultGlobalRuntime: IORuntime +} From af20aefe667e937b85873932bd3cf20a75adc147 Mon Sep 17 00:00:00 2001 From: Paul J Thordarson Date: Tue, 6 May 2025 09:18:01 -0400 Subject: [PATCH 3/5] DRY global runtime setup. --- .../js/src/main/scala/cats/effect/IOApp.scala | 24 ++--------------- .../src/main/scala/cats/effect/IOApp.scala | 24 ++--------------- .../src/main/scala/cats/effect/IOApp.scala | 24 ++--------------- .../main/scala/cats/effect/IOAppCommon.scala | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+), 66 deletions(-) diff --git a/core/js/src/main/scala/cats/effect/IOApp.scala b/core/js/src/main/scala/cats/effect/IOApp.scala index e97c548ddc..13532669b9 100644 --- a/core/js/src/main/scala/cats/effect/IOApp.scala +++ b/core/js/src/main/scala/cats/effect/IOApp.scala @@ -144,9 +144,6 @@ import scala.util.Try * [[IOApp.Simple]] */ trait IOApp extends IOAppPlatform { - - private[this] var _runtime: unsafe.IORuntime = null - /** * The runtime which will be used by `IOApp` to evaluate the [[IO]] produced by the `run` * method. This may be overridden by `IOApp` implementations which have extremely specialized @@ -160,7 +157,7 @@ trait IOApp extends IOAppPlatform { * * This value is guaranteed to be equal to [[unsafe.IORuntime.global]]. */ - protected def runtime: unsafe.IORuntime = _runtime + protected def runtime: unsafe.IORuntime = installedRuntime /** * The configuration used to initialize the [[runtime]] which will evaluate the [[IO]] @@ -202,24 +199,7 @@ trait IOApp extends IOAppPlatform { def run(args: List[String]): IO[ExitCode] final def main(args: Array[String]): Unit = { - val installed = if (runtime == null) { - import unsafe.IORuntime - - val installed = IORuntime installGlobal defaultGlobalRuntime - - _runtime = IORuntime.global - - installed - } else { - unsafe.IORuntime.installGlobal(runtime) - } - - if (!installed) { - System - .err - .println( - "WARNING: Cats Effect global runtime already initialized; custom configurations will be ignored") - } + setupGlobalRuntime() if (LinkingInfo.developmentMode && isStackTracing) { val listener: js.Function0[Unit] = () => diff --git a/core/jvm/src/main/scala/cats/effect/IOApp.scala b/core/jvm/src/main/scala/cats/effect/IOApp.scala index 58ed3ffff0..ea683fb41e 100644 --- a/core/jvm/src/main/scala/cats/effect/IOApp.scala +++ b/core/jvm/src/main/scala/cats/effect/IOApp.scala @@ -141,9 +141,6 @@ import java.util.concurrent.atomic.AtomicInteger * [[IOApp.Simple]] */ trait IOApp extends IOAppPlatform { - - private[this] var _runtime: unsafe.IORuntime = null - /** * The runtime which will be used by `IOApp` to evaluate the [[IO]] produced by the `run` * method. This may be overridden by `IOApp` implementations which have extremely specialized @@ -157,7 +154,7 @@ trait IOApp extends IOAppPlatform { * * This value is guaranteed to be equal to [[unsafe.IORuntime.global]]. */ - protected def runtime: unsafe.IORuntime = _runtime + protected def runtime: unsafe.IORuntime = installedRuntime /** * The configuration used to initialize the [[runtime]] which will evaluate the [[IO]] @@ -395,24 +392,7 @@ trait IOApp extends IOAppPlatform { val isForked = Thread.currentThread().getId() == 1 if (!isForked) onNonMainThreadDetected() - val installed = if (runtime == null) { - import unsafe.IORuntime - - val installed = IORuntime installGlobal defaultGlobalRuntime - - _runtime = IORuntime.global - - installed - } else { - unsafe.IORuntime.installGlobal(runtime) - } - - if (!installed) { - System - .err - .println( - "WARNING: Cats Effect global runtime already initialized; custom configurations will be ignored") - } + setupGlobalRuntime() if (isStackTracing) { val liveFiberSnapshotSignal = sys diff --git a/core/native/src/main/scala/cats/effect/IOApp.scala b/core/native/src/main/scala/cats/effect/IOApp.scala index 35c1500f4a..aa20e16f37 100644 --- a/core/native/src/main/scala/cats/effect/IOApp.scala +++ b/core/native/src/main/scala/cats/effect/IOApp.scala @@ -140,9 +140,6 @@ import java.util.concurrent.atomic.AtomicInteger * [[IOApp.Simple]] */ trait IOApp extends IOAppPlatform { - - private[this] var _runtime: unsafe.IORuntime = null - /** * The runtime which will be used by `IOApp` to evaluate the [[IO]] produced by the `run` * method. This may be overridden by `IOApp` implementations which have extremely specialized @@ -156,7 +153,7 @@ trait IOApp extends IOAppPlatform { * * This value is guaranteed to be equal to [[unsafe.IORuntime.global]]. */ - protected def runtime: unsafe.IORuntime = _runtime + protected def runtime: unsafe.IORuntime = installedRuntime /** * The configuration used to initialize the [[runtime]] which will evaluate the [[IO]] @@ -286,24 +283,7 @@ trait IOApp extends IOAppPlatform { def run(args: List[String]): IO[ExitCode] final def main(args: Array[String]): Unit = { - val installed = if (runtime == null) { - import unsafe.IORuntime - - val installed = IORuntime installGlobal defaultGlobalRuntime - - _runtime = IORuntime.global - - installed - } else { - unsafe.IORuntime.installGlobal(runtime) - } - - if (!installed) { - System - .err - .println( - "WARNING: Cats Effect global runtime already initialized; custom configurations will be ignored") - } + setupGlobalRuntime() val counter = new AtomicInteger(1) diff --git a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala index c23df9219a..9c2c2aff5b 100644 --- a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala +++ b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala @@ -19,5 +19,32 @@ package cats.effect import cats.effect.unsafe.IORuntime trait IOAppCommon { + this: IOApp => + + private[this] var _runtime: unsafe.IORuntime = null + + private[effect] def installedRuntime: unsafe.IORuntime = _runtime + private[effect] def defaultGlobalRuntime: IORuntime + + private[effect] def setupGlobalRuntime(): Unit = { + val installed = if (runtime == null) { + import unsafe.IORuntime + + val installed = IORuntime installGlobal defaultGlobalRuntime + + _runtime = IORuntime.global + + installed + } else { + unsafe.IORuntime.installGlobal(runtime) + } + + if (!installed) { + System + .err + .println( + "WARNING: Cats Effect global runtime already initialized; custom configurations will be ignored") + } + } } From c9f6e531c0d318ddaa46b67f845a5adbf8f0f800 Mon Sep 17 00:00:00 2001 From: Paul J Thordarson Date: Tue, 6 May 2025 10:00:56 -0400 Subject: [PATCH 4/5] Fixed inheritence problem. --- core/js/src/main/scala/cats/effect/IOApp.scala | 1 + core/jvm/src/main/scala/cats/effect/IOApp.scala | 1 + core/native/src/main/scala/cats/effect/IOApp.scala | 1 + core/shared/src/main/scala/cats/effect/IOAppCommon.scala | 4 ---- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/js/src/main/scala/cats/effect/IOApp.scala b/core/js/src/main/scala/cats/effect/IOApp.scala index 13532669b9..c838720be1 100644 --- a/core/js/src/main/scala/cats/effect/IOApp.scala +++ b/core/js/src/main/scala/cats/effect/IOApp.scala @@ -144,6 +144,7 @@ import scala.util.Try * [[IOApp.Simple]] */ trait IOApp extends IOAppPlatform { + /** * The runtime which will be used by `IOApp` to evaluate the [[IO]] produced by the `run` * method. This may be overridden by `IOApp` implementations which have extremely specialized diff --git a/core/jvm/src/main/scala/cats/effect/IOApp.scala b/core/jvm/src/main/scala/cats/effect/IOApp.scala index ea683fb41e..e169c9f6ba 100644 --- a/core/jvm/src/main/scala/cats/effect/IOApp.scala +++ b/core/jvm/src/main/scala/cats/effect/IOApp.scala @@ -141,6 +141,7 @@ import java.util.concurrent.atomic.AtomicInteger * [[IOApp.Simple]] */ trait IOApp extends IOAppPlatform { + /** * The runtime which will be used by `IOApp` to evaluate the [[IO]] produced by the `run` * method. This may be overridden by `IOApp` implementations which have extremely specialized diff --git a/core/native/src/main/scala/cats/effect/IOApp.scala b/core/native/src/main/scala/cats/effect/IOApp.scala index aa20e16f37..1c647224d9 100644 --- a/core/native/src/main/scala/cats/effect/IOApp.scala +++ b/core/native/src/main/scala/cats/effect/IOApp.scala @@ -140,6 +140,7 @@ import java.util.concurrent.atomic.AtomicInteger * [[IOApp.Simple]] */ trait IOApp extends IOAppPlatform { + /** * The runtime which will be used by `IOApp` to evaluate the [[IO]] produced by the `run` * method. This may be overridden by `IOApp` implementations which have extremely specialized diff --git a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala index 9c2c2aff5b..1453516d45 100644 --- a/core/shared/src/main/scala/cats/effect/IOAppCommon.scala +++ b/core/shared/src/main/scala/cats/effect/IOAppCommon.scala @@ -16,8 +16,6 @@ package cats.effect -import cats.effect.unsafe.IORuntime - trait IOAppCommon { this: IOApp => @@ -25,8 +23,6 @@ trait IOAppCommon { private[effect] def installedRuntime: unsafe.IORuntime = _runtime - private[effect] def defaultGlobalRuntime: IORuntime - private[effect] def setupGlobalRuntime(): Unit = { val installed = if (runtime == null) { import unsafe.IORuntime From e1a22179043672eb58393987487fb303847267d5 Mon Sep 17 00:00:00 2001 From: Paul J Thordarson Date: Tue, 6 May 2025 10:18:21 -0400 Subject: [PATCH 5/5] DRY MainThread --- .../cats/effect/IOAppMultiThreaded.scala | 16 +++++++++- .../src/main/scala/cats/effect/IOApp.scala | 32 ++----------------- .../scala/cats/effect/IOAppPlatform.scala | 27 +++++++++++++++- .../src/main/scala/cats/effect/IOApp.scala | 26 +-------------- .../scala/cats/effect/IOAppPlatform.scala | 22 ++++++++++++- 5 files changed, 65 insertions(+), 58 deletions(-) diff --git a/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala b/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala index 1e2aacb6b0..f9cdb74408 100644 --- a/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala +++ b/core/jvm-native/src/main/scala/cats/effect/IOAppMultiThreaded.scala @@ -16,4 +16,18 @@ package cats.effect -trait IOAppMultiThreaded {} +import scala.concurrent.ExecutionContext + +import java.util.concurrent.ArrayBlockingQueue + +trait IOAppMultiThreaded { + // arbitrary constant is arbitrary + private[effect] lazy val queue = new ArrayBlockingQueue[AnyRef](32) + + private[effect] def handleTerminalFailure(t: Throwable): Unit = { + queue.clear() + queue.put(t) + } + + private[effect] def defaultMainThread: ExecutionContext +} diff --git a/core/jvm/src/main/scala/cats/effect/IOApp.scala b/core/jvm/src/main/scala/cats/effect/IOApp.scala index e169c9f6ba..0ad165df8a 100644 --- a/core/jvm/src/main/scala/cats/effect/IOApp.scala +++ b/core/jvm/src/main/scala/cats/effect/IOApp.scala @@ -25,7 +25,7 @@ import cats.syntax.all._ import scala.concurrent.{blocking, CancellationException, ExecutionContext} import scala.concurrent.duration._ -import java.util.concurrent.{ArrayBlockingQueue, CountDownLatch} +import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicInteger /** @@ -196,14 +196,6 @@ trait IOApp extends IOAppPlatform { protected def computeWorkerThreadCount: Int = Math.max(2, Runtime.getRuntime().availableProcessors()) - // arbitrary constant is arbitrary - private[this] lazy val queue = new ArrayBlockingQueue[AnyRef](32) - - private[effect] def handleTerminalFailure(t: Throwable): Unit = { - queue.clear() - queue.put(t) - } - /** * Executes the provided actions on the JVM's `main` thread. Note that this is, by definition, * a single-threaded executor, and should not be used for anything which requires a meaningful @@ -219,27 +211,7 @@ trait IOApp extends IOAppPlatform { * calling thread (for example, LWJGL). In these scenarios, it is recommended that the * absolute minimum possible amount of work is handed off to the main thread. */ - protected def MainThread: ExecutionContext = - if (queue eq queue) - new ExecutionContext { - def reportFailure(t: Throwable): Unit = - t match { - case t if UnsafeNonFatal(t) => - IOApp.this.reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) - - case t => - handleTerminalFailure(t) - } - - def execute(r: Runnable): Unit = - if (!queue.offer(r)) { - runtime.blocking.execute(() => queue.put(r)) - } - } - else - throw new UnsupportedOperationException( - "Your IOApp's super class has not been recompiled against Cats Effect 3.4.0+." - ) + protected def MainThread: ExecutionContext = defaultMainThread /** * Configures the action to perform when unhandled errors are caught by the runtime. An diff --git a/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala b/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala index 91d40814a8..b862a5a4f2 100644 --- a/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/IOAppPlatform.scala @@ -16,11 +16,36 @@ package cats.effect -import cats.effect.unsafe.IORuntime +import cats.effect.unsafe.{IORuntime, UnsafeNonFatal} + +import scala.concurrent.ExecutionContext trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded { this: IOApp => + private[effect] def defaultMainThread: ExecutionContext = { + if (queue eq queue) + new ExecutionContext { + def reportFailure(t: Throwable): Unit = + t match { + case t if UnsafeNonFatal(t) => + IOAppPlatform.this.reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) + + case t => + handleTerminalFailure(t) + } + + def execute(r: Runnable): Unit = + if (!queue.offer(r)) { + runtime.blocking.execute(() => queue.put(r)) + } + } + else + throw new UnsupportedOperationException( + "Your IOApp's super class has not been recompiled against Cats Effect 3.4.0+." + ) + } + private[effect] def defaultGlobalRuntime: IORuntime = { val (compute, poller, compDown) = IORuntime.createWorkStealingComputeThreadPool( diff --git a/core/native/src/main/scala/cats/effect/IOApp.scala b/core/native/src/main/scala/cats/effect/IOApp.scala index 1c647224d9..c783373e70 100644 --- a/core/native/src/main/scala/cats/effect/IOApp.scala +++ b/core/native/src/main/scala/cats/effect/IOApp.scala @@ -24,7 +24,6 @@ import cats.syntax.all._ import scala.concurrent.{blocking, CancellationException, ExecutionContext} import scala.scalanative.meta.LinktimeInfo._ -import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.atomic.AtomicInteger /** @@ -195,14 +194,6 @@ trait IOApp extends IOAppPlatform { protected def computeWorkerThreadCount: Int = Math.max(2, Runtime.getRuntime().availableProcessors()) - // arbitrary constant is arbitrary - private[this] lazy val queue = new ArrayBlockingQueue[AnyRef](32) - - private[this] def handleTerminalFailure(t: Throwable): Unit = { - queue.clear() - queue.put(t) - } - /** * Executes the provided actions on the main thread. Note that this is, by definition, a * single-threaded executor, and should not be used for anything which requires a meaningful @@ -218,22 +209,7 @@ trait IOApp extends IOAppPlatform { * calling thread (for example, LWJGL). In these scenarios, it is recommended that the * absolute minimum possible amount of work is handed off to the main thread. */ - protected def MainThread: ExecutionContext = - new ExecutionContext { - def reportFailure(t: Throwable): Unit = - t match { - case t if UnsafeNonFatal(t) => - IOApp.this.reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) - - case t => - handleTerminalFailure(t) - } - - def execute(r: Runnable): Unit = - if (!queue.offer(r)) { - runtime.blocking.execute(() => queue.put(r)) - } - } + protected def MainThread: ExecutionContext = defaultMainThread /** * Configures the action to perform when unhandled errors are caught by the runtime. An diff --git a/core/native/src/main/scala/cats/effect/IOAppPlatform.scala b/core/native/src/main/scala/cats/effect/IOAppPlatform.scala index d7e207a8ba..2cf689b336 100644 --- a/core/native/src/main/scala/cats/effect/IOAppPlatform.scala +++ b/core/native/src/main/scala/cats/effect/IOAppPlatform.scala @@ -16,11 +16,31 @@ package cats.effect -import cats.effect.unsafe.IORuntime +import cats.effect.unsafe.{IORuntime, UnsafeNonFatal} + +import scala.concurrent.ExecutionContext trait IOAppPlatform extends IOAppCommon with IOAppMultiThreaded { this: IOApp => + private[effect] def defaultMainThread: ExecutionContext = { + new ExecutionContext { + def reportFailure(t: Throwable): Unit = + t match { + case t if UnsafeNonFatal(t) => + IOAppPlatform.this.reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime) + + case t => + handleTerminalFailure(t) + } + + def execute(r: Runnable): Unit = + if (!queue.offer(r)) { + runtime.blocking.execute(() => queue.put(r)) + } + } + } + private[effect] def defaultGlobalRuntime: IORuntime = { val (compute, poller, compDown) = IORuntime.createWorkStealingComputeThreadPool(