Skip to content

Propagate fatal errors from async/cont callbacks - #4653

Open
arimu1 wants to merge 1 commit into
typelevel:series/3.6.xfrom
arimu1:fix/4505-async-fatal-propagation
Open

Propagate fatal errors from async/cont callbacks#4653
arimu1 wants to merge 1 commit into
typelevel:series/3.6.xfrom
arimu1:fix/4505-async-fatal-propagation

Conversation

@arimu1

@arimu1 arimu1 commented Aug 7, 2026

Copy link
Copy Markdown

Description

Fixes #4505

Fatal errors (OutOfMemoryError and other non-UnsafeNonFatal throwables) delivered through an async/cont resume callback were completed as Outcome.Errored on the fiber instead of crashing the process. That differed from IO.raiseError(fatal) and from fatals thrown inside IO.delay / IO thunks, which already call onFatalFailure.

This shows up with IO.fromCompletableFuture because CompletableFuture.handle catches the fatal and resumes with Left(t), and also with a minimized form:

IO.async_[Unit] { cb => cb(Left(new OutOfMemoryError("Boom!"))) }

Approach

Detect fatal errors as soon as the cont/async result is consumed by the runloop, matching the existing Error / raiseError case:

  • asyncContinueFailedR — async resume with Left(fatal)
  • IOCont.Get when the callback already completed — synchronous cont completion with Left(fatal)

No change to fromCompletableFuture itself: rethrowing inside CompletableFuture.handle is unnecessary once the fiber treats the resumed fatal correctly, and avoids leaving the cont suspended if the throw is mishandled on the CF thread.

Prior attempt #4512 targeted a similar issue but was closed (hanging tests / abandoned). This PR only touches the fiber paths that consume the failure.

Testing

  • Added IOApp process tests (recommended for fatals so the shared test runtime is not shut down):
    • exit on fatal error from async_
    • exit on fatal error from CompletableFuture (JVM)
  • Local: sbt ioAppTestsJVM/testOnly cats.effect.IOAppSpec28 examples, 0 failure (Temurin 21), including both new cases

OutOfMemoryError and other fatal Throwables delivered via cont/async
resume (including IO.fromCompletableFuture) completed as Outcome.Errored
instead of crashing the process like IO.raiseError.

Detect fatals in asyncContinueFailedR and the synchronous cont Get path,
matching the Error/raiseError handling in the runloop.

Fixes typelevel#4505

@selormwalker selormwalker left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvements! Ready for merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants