github typelevel/cats-effect v3.0.0-M5

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

Cats Effect 3.0.0-M5 is the fourth 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 "M5" 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 M4 to M5. For a more complete summary of the changes generally included in Cats Effect 3.0.0, please see the M1, M2, M3, and M4 release notes.

Major Changes

Supervisor

In practice, it is very common to need more control over fiber lifecycles than the basic combination of start, join, and cancel. While these are nice primitives, it is very definitely worth building higher level functionality on top which can allow for greater power and expressiveness. background is a very simple example of such a combinator, but we're beginning to explore the space of even richer abstractions.

Supervisor is one such tool. The target scenario here is when it is necessary to spawn a fiber within some scope which is managed and controlled separately. This is useful for two things. First, Supervisor guarantees that when it is canceled, all supervised fibers will also be canceled. Second, any fibers started by the Supervisor will be located within the origin scope of the Supervisor itself, which gives them access to the then-current ExecutionContext as well as any other lexically scoped elements of the effect (such as if Kleisli is in use). One example of where this is useful is in a server receiving incoming client connections and needing to spawn fibers to manage those connections.

Resource#makeFull

The make constructor is responsible for creating a Resource given an effect which allocates a resource of type A, along with a function which disposes of that resource when the scope is exited. This is analogous to the bracket function, which behaves similarly. With CE3, bracket itself has been generalized considerably, and is itself a derived combinator implemented in terms of uncancelable and onCancel. This has given rise to richer combinators such as bracketFull, which passes a Poll to its acquisition effect.

The addition of the Poll to resource acquisition allows the composition of richer resource semantics which internally require semantic blocking. The classic example of this is Semaphore acquisition, which is logically a resource that must be released when finished, but where the acquisition of that resource may require blocking (if no permits are available). Safely interruptible resource acquisition is exactly the problem that Poll was designed to solve. With the addition of the makeFull constructor on Resource, we now have the ability to define such interruptible acquisition resources as regions rather than simply brackets. For example, acquiring a Semaphore permit as a Resource is now an operation which can be defined in user-space:

Resource.eval(Semaphore[IO](1)) flatMap { s =>
  // ...
  Resource.makeFull(_(s.acquire))(_ => s.release)  // => Resource[IO, Unit]
}

When the inner Resource in the above is closed, the Semaphore permit will be released. If the permit is unavailable when the acquire is run, the fiber will semantically block, but that blocking will be interruptible and will not prevent cancellation.

The one downside to this change is the requisite addition of a tighter set of constraints on mapK. Whereas previously this function was unconstrained on both F and G (the input and output effects), the implementation must now internally use uncancelable from the MonadCancel typeclass, meaning that it is now constrained on MonadCancel[F, _] and MonadCancel[G, _].

Eliminated Effect Covariance on Resource

One subtler change introduced in this milestone is a revision to the Resource type signature. It is now defined as the following:

sealed abstract class Resource[F[_], +A]

Previously, the F[_] was defined as +F[_], which made it somewhat easier to interoperate with polyfunctor polymorphic interfaces leveraging subtyping. Unfortunately, this encoding appears to be generally impossible to implement without unsound operations and casting, which is quite a bad sign. It has also historically surfaced serious bugs in Scala 2, even leading to downstream encodings that are unsound.

For these and several other reasons, the decision was made to eliminate the variance on the effect itself. The A remains (rightly) covariant and does not cause any of the same issues, due to the transitive Functor[F] constraint on interpretation.

Ongoing Scheduler Performance Improvements

Not satisfied with a roughly 2x - 5x performance improvement on fiber scheduling overhead under contention, Vasil has been working hard on improving the IO fiber scheduling implementation even further. This is an active area of research, where we are currently experimenting not just with performance enhancements but also with algorithms which can detect bugs related to blocking on the compute pool and appropriately surface those problems to users, making it easier to squash hard-to-detect issues before they hit production.

This is all ongoing work, but in the meantime the performance has continued to inch forward. At the present time, in our benchmarks, the IO work-stealing scheduler is almost exactly 11x faster under contention than a naive scheduler written against a fixed thread pool (i.e. the CE2 scheduler). Needless to say, we're looking forward to seeing this in production applications!

Pull Requests

You're all amazing, thank you!

Don't miss a new cats-effect release

NewReleases is sending notifications on new releases.