Cats Effect 3.0.0-RC1 is the first release candidate for 3.0.0. It is expected that one or two more release candidates will be required prior to the final release of 3.0.0, but none are specifically planned at this time. If everything goes perfectly and the ecosystem does not uncover problems, the next release will be 3.0.0 itself.
To that end, we will be attempting to maintain backward compatibility within the 3.0.0-RC lineage leading up to 3.0.0 final. This is not a guarantee and we are not enabling MiMa checks, but the intention is that any further release candidates will be binary compatible with RC1 so as to ease the burden on the ecosystem. No further changes are expected to the API, only additions.
What follows are the changes from M5 to RC1. For a more complete summary of the changes generally included in Cats Effect 3.0.0, please see the M1, M2, M3, M4, and M5 release notes.
Major Changes
Async
Instance for Resource
One of the original motivating use-cases behind some of the early thinking around Cats Effect 3 was the following: assume you have two Resource
s, race
them such that the winner is produced and remains open while the loser is cleanly finalized. As it turns out, this semantic is not possible on Cats Effect 2 (without manual bespoke implementation work using allocated
), and this fact is honestly quite limiting.
While it is not entirely difficult to implement Resource#race
as a special-cased thing, the truly general solution to this limitation, and all similar limitations, is to define Async[Resource[F, *]]
given Async[F]
, which in turn also means defining mechanisms for concurrency in terms of Resource
itself. This is a surprisingly tricky problem, since defining the meaning of Fiber
in the presence of dynamically-scoped finalizers is not trivial.
The original proposals for Cats Effect 3 involved a proposed typeclass, Region
, which was intended to address this issue. In particular, Region
defined what it meant to form a dynamic monadic region, in much the same way as Bracket
defined what it meant to form a static monadic region. Ultimately, it was decided to remove Region
for several reasons, the first of which being that it introduced a considerable amount of complexity into the hierarchy and stood in the way of a number of usability goals.
But even more than usability (which is important!), it was convincingly argued that Region
was in fact redundant: every Region
can be encoded in terms of an underlying Bracket
, and Bracket
in turn can be encoded in terms of a Region
which is opened and then immediately closed. Thus, Region
offered no theoretical expressive power beyond Bracket
, which in turn could then be made non-primitive (due to the removal of Region
), paving the way for the current uncancelable
/onCase
/handleErrorWith
encoding which exists in the system today.
This argument though implied something very strong: that it was possible to implement the entire typeclass hierarchy for any dynamic monadic region. Now that Async
has been implemented for Resource
, we have much stronger evidence that this is the case.
The exact semantics of Async[Resource]
are subtle in a few ways that are worth calling out:
- Any scopes that are opened with an
async
block are maintained after the continuation of theasync
. This may be a little unintuitive, but it's the safest default given that resources allocated during the registration of an asynchronous action may be critical for the validity of the results once produced. - When a resource is
start
ed, its finalizers become part of the outer scope. This is to ensure that finalization is run even when the fiber is notjoin
ed. There are arguably at least three reasonable semantics which could have been chosen in this area, and in the end, the tie-breaker was that original motivating use case:race
ing twoResource
s and closing the loser. onCancel
is very different fromResource.make
in that it inserts a finalization boundary which is not extended byflatMap
. However, despite this it can still be rendered withinResource
through the use ofapplyFull
andallocated
, which allow for the encoding of arbitrary scope boundary semantics. (credit to @RaasAhsan for this insight)
As a final note, it is worth calling out the fact that the memoize
function, which is available on any Concurrent
, does not behave on Resource
in the way you might intuitively expect. In particular, if you use
the inner Resource
which arises from the memoization, the finalizers will be run and the memoized value (which can be re-accessed) may then be invalidated. This is an unavoidable consequence of the lack of linearity in Scala's type system. Ultimately, it is use
itself which is unsafe; in its absence, memoize
actually behaves as you would expect. use
is the operation which cannot be appropriately alias-constrained due to these limitations in Scala, and thus it is to be expected that there are some outsized consequences which cannot be definitively prevented.
Configurable Root Cancelation Scope in MonadCancel
One of the interesting things about cancelation in Cats Effect is that it is a hint. This is somewhat fundamental to the model, which attempts to preserve both safety and preemption, but it is nevertheless interesting and it has some major impacts on the nature of the APIs we can implement. One such impact is the fact that MonadCancel[F].canceled
produces an F[Unit]
, not an F[Nothing]
, which might be more intuitive. The reason for this is the fact that even self-cancelation is not guaranteed to be respected, since we might be nested within an uncancelable
block. Similarly Fiber#cancel
is not guaranteed to be immediately respected, only eventually respected if the fiber is not permanently blocked within an uncancelable
region.
One perhaps-surprising implication of this is that it is technically possible to validly implement MonadCancel
without implementing any cancelation support! This is because it is always safe to ignore the cancelation hint and pretend to be permanently uncancelable
. There is still some expressiveness implied by MonadCancel
which is more powerful than just MonadError
(namely, forceR
), but this is relatively narrow in scope.
In this release, we made the decision to allow this "optionality" of MonadCancel
to be more directly reflected in the runtime, in the form of the MonadCancel[F].rootCancelScope
value. This had a number of helpful consequences, most notable of which the fact that Sync
is able to extend MonadCancel
without forcing implementations such as SyncIO
to implement cancelation without fibers. This in turn is very helpful because it allows users and frameworks to write code which is safe in the presence of cancelation, but which does not assume cancelability.
Splitting these semantics apart generalizes MonadCancel
just enough that these kinds of cases are possible, and we avoid ugly pathologies like F[_]: MonadCancel: Sync
, which was not uncommon in many corners of the ecosystem prior to this change.
Added the Unique
Typeclass
At first glance, Unique
appears to be a relatively ad-hoc class to add into the hierarchy. It defines a single method, unique
, which produces an F[Unique.Token]
, where Hash[Unique.Token]
is defined. It has only one law, which defines that all sequencings of unique
must produce differentiably unique tokens. In other words, arbitrary unique identity.
Again, this seems like a relatively arbitrary thing to add into the hierarchy until you dig into it a little more deeply. For starters, uniquely differentiable values imply some relatively strong things about the effect type, F
. It must not memoize, and if the values are differentiable using the JVM's own inherent notion of identity (which is the fastest method for achieving this property in general), it must have some notion of lazy evaluation. These are relatively stringent properties, and they are in fact must stronger than even Defer
.
What is also interesting is the fact that Spawn
already implies that F
must have these properties. In particular due to the fact that Fiber
instances are unique by definition: start
ing multiple times results in independent Fiber
s, all of which have independent cancel
and join
methods, and so on. Spawn
certainly implies a number of things that are stronger than just the properties necessary to represent uniqueness, but it's at least something in that direction.
This, combined with the fact that uniqueness is a property of the JVM and JavaScript runtimes is relatively strong evidence that this is something that, like Clock
, should be reflected in the abstractions. Unique
fills that niche, and in so doing, enables the kinds of patterns that were already necessary within libraries like Vault and Fs2, now natively within the Cats Effect runtime calculus.
User-Facing Pull Requests
- #1677 – Added utilities for measuring effect time (@patseev)
- #1682 – Null out reference to last stolen fiber (@vasilmkd)
- #1674 – Updated to Cats 2.4.1 (@djspiewak)
- #1670 – Fix
Supervisor
cleanup race (@RaasAhsan) - #1665 – Improved auto-
cede
continuation (@vasilmkd) - #1662 – Value forwarders for
*Throw
instances (@TimWSpence) - #1589 – Added iteration combinators from cats core to
IO
(@pkowalcze) - #1651 – Renamed
Resource#parZip
toboth
for consistency (@djspiewak) - #1649 – Implicits don't need to be package objects (@rossabaker)
- #1636 – Reencoded
Get
finalizer (@vasilmkd) - #1635 –
Unique
typeclass (@djspiewak) - #1634 – New
ContState
implementation to remove allocations (@vasilmkd) - #1625 –
fromCompletableFuture
: unwrapCompletionExceptions
(@amesgen) - #1575 – Addeded
Resource#flattenK
(@djspiewak) - #1565 – Implemented
Async[Resource[F, *]]
givenAsync[F]
(@djspiewak) - #1616 –
Invariant
forDequeue
(@TimWSpence) - #1614 – Sink and source for
Dequeue
(@TimWSpence) - #1600 –
IOFiber
extendsAtomicBoolean
(@vasilmkd) - #1597 – Properly publish done resume tag (@vasilmkd)
- #1552 – Memoizing a canceled effect should result in
canceled
(@wemrysi) - #1577 – Avoid integer overflow in
TestInstances#unsafeRun
(@mpilquist) - #1553 – Add supertypes for concurrent types (@RaasAhsan)
- #1572 – Revise
IO.bracketCase
and overrideIO.bracketFull
(@heka1024) - #1561 – Don't terminate JVM forcibly in
IOApp
onExitCode.Success
(@rkrzewski) - #1556 – Extract useful test instances from cats-effect-core test suite (@mpilquist)
- #1567 – Fix
MonadCancel
syntax type inference (@joroKr21) - #1570 – Add
Dispatcher#unsafeRunCancelable
(@RaasAhsan) - #1546 – Configurable root cancelation scope in
MonadCancel
(@djspiewak) - #1547 – Corrected issue with extremely fast
IOApp
shutdown (@djspiewak) - #1549 – Revise finalizer error semantics to be consistent with
try
/finally
(@RaasAhsan) - #1543 – Inlining usages of
require
inQueue
andPQueue
(@jyoo980) - #1536 – Handle uncaught errors in
bracket
(@RaasAhsan) - #1541 – Renaming functions in Fiber (e.g.
joinAndEmbedNever
tojoinWithNever
) (@jyoo980) - #1538, #1588, #1591, #1607, #1622, #1644, #1648 – Documentation (@RaasAhsan, @ashwinbhaskar, @TimWSpence, @djspiewak)
Special thanks to each and every one of you!
Extra-special thanks are due to @TimWSpence, who has been tirelessly tackling the task of documenting Cats Effect 3!