From 096b89634b6f3e7694412b25544c3fe487d885c2 Mon Sep 17 00:00:00 2001 From: Daniel Urban Date: Thu, 25 Apr 2024 19:25:14 +0200 Subject: [PATCH 1/3] Add failing test --- .../test/scala/cats/effect/IOAppSpec.scala | 7 ++++ .../src/main/scala/catseffect/examples.scala | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala b/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala index be555ea617..a440824476 100644 --- a/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala @@ -285,6 +285,13 @@ class IOAppSpec extends Specification { h.stderr() must not(contain("Promise already completed")) } + "shut down WSTP on fatal error without IOApp" in { + val h = platform(FatalErrorShutsDownRt, List.empty) + h.awaitStatus() + h.stdout() must not(contain("sadness")) + h.stdout() must contain("done") + } + "exit on canceled" in { val h = platform(Canceled, List.empty) h.awaitStatus() mustEqual 1 diff --git a/tests/shared/src/main/scala/catseffect/examples.scala b/tests/shared/src/main/scala/catseffect/examples.scala index 77967346c3..257bf707e6 100644 --- a/tests/shared/src/main/scala/catseffect/examples.scala +++ b/tests/shared/src/main/scala/catseffect/examples.scala @@ -21,10 +21,13 @@ import cats.effect.std.{Console, Random} import cats.effect.unsafe.{IORuntime, IORuntimeConfig, Scheduler} import cats.syntax.all._ +import scala.concurrent.Await import scala.concurrent.duration._ package examples { + import java.util.concurrent.TimeoutException + object HelloWorld extends IOApp.Simple { def run: IO[Unit] = IO(println("Hello, World!")) @@ -58,6 +61,36 @@ package examples { } } + object FatalErrorShutsDownRt extends RawApp { + def main(args: Array[String]): Unit = { + val rt = cats.effect.unsafe.IORuntime.global + @volatile var thread: Thread = null + val action = for { + // make sure a blocking thread exists, save it: + _ <- IO.blocking { + thread = Thread.currentThread() + } + // get back on the WSTP: + _ <- IO.cede + // fatal error on the WSTP thread: + _ <- IO { + throw new OutOfMemoryError("Boom!") + }.attempt.flatMap(_ => IO.println("sadness (attempt)")) + } yield () + val fut = action.unsafeToFuture()(rt) + try { + Await.ready(fut, atMost = 2.seconds) + } catch { + case _: TimeoutException => println("sadness (timeout)") + } + Thread.sleep(500L) + // by now the WSTP (and all its threads) must've been shut down: + if (thread eq null) println("sadness (thread is null)") + else if (thread.isAlive()) println("sadness (thread is alive)") + println("done") + } + } + object RaiseFatalErrorAttempt extends IOApp { def run(args: List[String]): IO[ExitCode] = { IO.raiseError[Unit](new OutOfMemoryError("Boom!")) From 6e5b89c0a9b75104bf856bfa578c34f8a0582933 Mon Sep 17 00:00:00 2001 From: Daniel Urban Date: Thu, 25 Apr 2024 19:30:49 +0200 Subject: [PATCH 2/3] Shut down executors when IORuntime.global shuts down --- .../effect/unsafe/IORuntimeCompanionPlatform.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeCompanionPlatform.scala b/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeCompanionPlatform.scala index f0df6cca33..c0019536f2 100644 --- a/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeCompanionPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeCompanionPlatform.scala @@ -195,10 +195,15 @@ private[unsafe] abstract class IORuntimeCompanionPlatform { this: IORuntime.type def global: IORuntime = { if (_global == null) { installGlobal { - val (compute, _) = createWorkStealingComputeThreadPool() - val (blocking, _) = createDefaultBlockingExecutionContext() + val (compute, computeDown) = createWorkStealingComputeThreadPool() + val (blocking, blockingDown) = createDefaultBlockingExecutionContext() + val shutdown = () => { + computeDown() + blockingDown() + resetGlobal() + } - IORuntime(compute, blocking, compute, () => resetGlobal(), IORuntimeConfig()) + IORuntime(compute, blocking, compute, shutdown, IORuntimeConfig()) } } From 1e554c4f286cdab69283160c34ada59330bc3a59 Mon Sep 17 00:00:00 2001 From: Daniel Urban Date: Thu, 25 Apr 2024 20:34:50 +0200 Subject: [PATCH 3/3] We don't need to run this on nodejs --- .../jvm/src/test/scala/cats/effect/IOAppSpec.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala b/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala index a440824476..c6718b38e1 100644 --- a/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala @@ -285,13 +285,6 @@ class IOAppSpec extends Specification { h.stderr() must not(contain("Promise already completed")) } - "shut down WSTP on fatal error without IOApp" in { - val h = platform(FatalErrorShutsDownRt, List.empty) - h.awaitStatus() - h.stdout() must not(contain("sadness")) - h.stdout() must contain("done") - } - "exit on canceled" in { val h = platform(Canceled, List.empty) h.awaitStatus() mustEqual 1 @@ -342,6 +335,13 @@ class IOAppSpec extends Specification { ok } + "shut down WSTP on fatal error without IOApp" in { + val h = platform(FatalErrorShutsDownRt, List.empty) + h.awaitStatus() + h.stdout() must not(contain("sadness")) + h.stdout() must contain("done") + } + "support main thread evaluation" in { val h = platform(EvalOnMainThread, List.empty) h.awaitStatus() mustEqual 0