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
- #1528 – Clarify async sequenced laws and restore (weaker)
Future
tests (@wemrysi) - #1526 – Finalize, if necessary, when exiting an
uncancelable
region (@wemrysi) - #1488 –
Supervisor
(@RaasAhsan) - #1523 – Revised
allocated
identity property (@djspiewak) - #1484 – Handle self-cancellation when running fibers to avoid deadlocks (@wemrysi)
- #1454 – Add
ExitCase.toOutcome
for completeness (@joroKr21) - #1493 – Added
Resource#makeFull
and removed effect covariance (@SystemFw) - #1518 – Updated to Scala 3.0.0-M3 (@mpilquist)
- #1509 – fix inference for default
show
args (@tpolecat) - #1490 – Remove buggy circular buffer (@vasilmkd)
- #1483 – Synchronize directly on the object (@vasilmkd)
- #1478 – Add
Random
(@ChristopherDavenport) - #1495 – Reintroduce
IO.Par[A]
as an alias in theIO
companion object (@arosien) - #1476 – Fix migration warnings on Scala 3 (@vasilmkd)
- #1470 – Non-blocking external queue (@vasilmkd)
- #1466 – Circular buffer for parked worker threads and removed lock (@vasilmkd)
- #1480 – Trim some
IOFiber
mutable fat (@vasilmkd) - #1465 – Implement
Clock#applicative
inSync
andGenTemporal
(@joroKr21) - #1462 – Remove duplicate test from
RefSpec
(@davidabrahams) - #1458 – More
parTraverseN
(@SystemFw)
You're all amazing, thank you!