diff --git a/core/shared/src/main/scala/cats/effect/IOFiber.scala b/core/shared/src/main/scala/cats/effect/IOFiber.scala index f6214b50f8..ffea35d49c 100644 --- a/core/shared/src/main/scala/cats/effect/IOFiber.scala +++ b/core/shared/src/main/scala/cats/effect/IOFiber.scala @@ -241,7 +241,11 @@ private final class IOFiber[A]( case 1 => val cur = cur0.asInstanceOf[Error] - runLoop(failed(cur.t, 0), nextCancelation, nextAutoCede) + val ex = cur.t + if (!NonFatal(ex)) + onFatalFailure(ex) + + runLoop(failed(ex, 0), nextCancelation, nextAutoCede) case 2 => val cur = cur0.asInstanceOf[Delay[Any]] @@ -315,7 +319,11 @@ private final class IOFiber[A]( case 1 => val error = ioe.asInstanceOf[Error] - runLoop(failed(error.t, 0), nextCancelation - 1, nextAutoCede) + val ex = error.t + if (!NonFatal(ex)) + onFatalFailure(ex) + + runLoop(failed(ex, 0), nextCancelation - 1, nextAutoCede) case 2 => val delay = ioe.asInstanceOf[Delay[Any]] @@ -382,7 +390,11 @@ private final class IOFiber[A]( case 1 => val error = ioe.asInstanceOf[Error] - runLoop(failed(error.t, 0), nextCancelation - 1, nextAutoCede) + val ex = error.t + if (!NonFatal(ex)) + onFatalFailure(ex) + + runLoop(failed(ex, 0), nextCancelation - 1, nextAutoCede) case 2 => val delay = ioe.asInstanceOf[Delay[Any]] @@ -434,6 +446,8 @@ private final class IOFiber[A]( case 1 => val error = ioa.asInstanceOf[Error] val t = error.t + if (!NonFatal(t)) + onFatalFailure(t) // We need to augment the exception here because it doesn't get // forwarded to the `failed` path. Tracing.augmentThrowable(runtime.enhancedExceptions, t, tracingEvents) diff --git a/tests/js/src/main/scala/catseffect/examplesplatform.scala b/tests/js/src/main/scala/catseffect/examplesplatform.scala index 44081e467f..ba5a1a743e 100644 --- a/tests/js/src/main/scala/catseffect/examplesplatform.scala +++ b/tests/js/src/main/scala/catseffect/examplesplatform.scala @@ -45,6 +45,10 @@ package examples { register(Arguments) register(NonFatalError) register(FatalError) + register(RaiseFatalErrorAttempt) + register(RaiseFatalErrorHandle) + register(RaiseFatalErrorMap) + register(RaiseFatalErrorFlatMap) registerRaw(FatalErrorRaw) register(Canceled) registerLazy("catseffect.examples.GlobalRacingInit", GlobalRacingInit) diff --git a/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala b/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala index 82b75f7a1a..39cdda2de5 100644 --- a/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/IOAppSpec.scala @@ -187,6 +187,34 @@ class IOAppSpec extends Specification { h.stderr() must contain("Boom!") } + "exit on raising a fatal error with attempt" in { + val h = platform(RaiseFatalErrorAttempt, List.empty) + h.awaitStatus() mustEqual 1 + h.stderr() must contain("Boom!") + h.stdout() must not(contain("sadness")) + } + + "exit on raising a fatal error with handleError" in { + val h = platform(RaiseFatalErrorHandle, List.empty) + h.awaitStatus() mustEqual 1 + h.stderr() must contain("Boom!") + h.stdout() must not(contain("sadness")) + } + + "exit on raising a fatal error inside a map" in { + val h = platform(RaiseFatalErrorMap, List.empty) + h.awaitStatus() mustEqual 1 + h.stderr() must contain("Boom!") + h.stdout() must not(contain("sadness")) + } + + "exit on raising a fatal error inside a flatMap" in { + val h = platform(RaiseFatalErrorFlatMap, List.empty) + h.awaitStatus() mustEqual 1 + h.stderr() must contain("Boom!") + h.stdout() must not(contain("sadness")) + } + "warn on global runtime collision" in { val h = platform(GlobalRacingInit, List.empty) h.awaitStatus() mustEqual 0 diff --git a/tests/shared/src/main/scala/catseffect/examples.scala b/tests/shared/src/main/scala/catseffect/examples.scala index d1262bec53..682302a047 100644 --- a/tests/shared/src/main/scala/catseffect/examples.scala +++ b/tests/shared/src/main/scala/catseffect/examples.scala @@ -58,6 +58,44 @@ package examples { } } + object RaiseFatalErrorAttempt extends IOApp { + def run(args: List[String]): IO[ExitCode] = { + IO.raiseError[Unit](new OutOfMemoryError("Boom!")) + .attempt + .flatMap(_ => IO.println("sadness")) + .as(ExitCode.Success) + } + } + + object RaiseFatalErrorHandle extends IOApp { + def run(args: List[String]): IO[ExitCode] = { + IO.raiseError[Unit](new OutOfMemoryError("Boom!")) + .handleError(_ => ()) + .flatMap(_ => IO.println("sadness")) + .as(ExitCode.Success) + } + } + + object RaiseFatalErrorMap extends IOApp { + def run(args: List[String]): IO[ExitCode] = { + IO.raiseError[Unit](new OutOfMemoryError("Boom!")) + .map(_ => ()) + .handleError(_ => ()) + .flatMap(_ => IO.println("sadness")) + .as(ExitCode.Success) + } + } + + object RaiseFatalErrorFlatMap extends IOApp { + def run(args: List[String]): IO[ExitCode] = { + IO.raiseError[Unit](new OutOfMemoryError("Boom!")) + .flatMap(_ => IO(())) + .handleError(_ => ()) + .flatMap(_ => IO.println("sadness")) + .as(ExitCode.Success) + } + } + object Canceled extends IOApp { def run(args: List[String]): IO[ExitCode] = IO.canceled.as(ExitCode.Success)