github typelevel/cats-effect v3.0.0-M3

latest releases: v3.6.0-RC2, v3.6.0-RC1, v3.5.7...
pre-release4 years ago

Cats Effect 3.0.0-M3 is the third milestone release in the 3.0.0 line. We are expecting to follow this milestone with subsequent ones as necessary to further refine functionality and features, prior to some number of release candidates leading to a final release. The designation "M3" is meant to indicate our relative confidence in the functionality and API of the library, but it by no means indicates completeness or final compatibility. Neither binary compatibility nor source compatibility with anything are assured prior to 3.0.0, though major breakage is unlikely at this point.

What follows are the changes from M2 to M3. For a more complete summary of the changes generally included in Cats Effect 3.0.0, please see the M1 and M2 release notes.

Major Changes

Error Trapping

One of the "skeletons in the closet" for nearly all thread pools is fatal error trapping. There are a whole series of exceptions which can be raised by the JVM which represent varying levels of serious problems (most famously, OutOfMemoryError). These exceptions are correctly ignored by IO and similar runtime systems, but unfortunately that doesn't really solve the problem for the user.

Consider a scenario wherein an IO-using application runs out of memory. When this happens, the error will be raised by the VM, which in turn will result in the carrier thread being killed and the active fiber deadlocking. Unfortunately, without further intervention, that's all that will happen. Other fibers can continue executing, and the VM may even try to recover from the error (which is actually unsound).

Similar things happen with link errors, stack overflows, and even thread interruption (which frequently occurs when using sbt run). In all of these cases, the carrier thread is gone, at least one fiber is deadlocked, and the rest of the application has no idea. If you're lucky the fatal exception will have its stack trace printed, but it's easy to miss this in the logs, meaning that zombie processes are actually quite a common consequence of this situation.

Unfortunately, this is an incredibly difficult problem to solve because fatal errors, by definition, cannot be reliably handled. The VM is often in an undefined state and even basic things like allocation or virtual dispatch may not behave normally. Thus, the runtime needs to achieve a full shutdown and propagation of the error across threads, but without actually doing anything fancy.

The solution we've chosen here is to take the following steps any time a fatal error is trapped:

  1. Print the stack trace
  2. Trigger a full shutdown of the runtime thread pool
  3. Asynchronously propagate the error back to the original unsafeRun... call site

In most cases, this means that the error will be relocated back to the original unsafeRun call site, which in turn will mean that errors will shut down the containing application and avoid the zombie scenario. In a worst-case, the entire application will be halted and the error will at least be printed.

This entirely resolves the "zombie process" problem, even in the face of an OutOfMemoryError.

Respecialized racePair

racePair can be implemented entirely in terms of Deferred and Fiber, meaning that it does not need to be a primitive in the IO algebra. This is actually relatively significant since, as it turns out, the implementation in terms of Deferred and Fiber is faster than the "primitive" as it had been implemented in IO itself. This in turn increases the performance of Parallel and related operations.

Pull Requests

You're all amazing, thank you!

Don't miss a new cats-effect release

NewReleases is sending notifications on new releases.