typelevel / typelevel/cats-effect
Introduce "inclusive" `race` as new default semantic
Nobody has claimed this yet.
- Dominant language
- Scala
- Stars
- 2.2k
- Forks
- 576
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 18
Description
Full discussion here: https://discord.com/channels/632277896739946517/839263556754472990/1078054700932419695
The short version is that race, in its current form, represents a resource leak. If there's a tie (i.e. f.cancel *> f.join does not produce Outcome.Canceled()), then it's ambiguous as to what result should be chosen. Right now, we technically bias to the Left, but this is just an implementation detail made manifest. Even worse, if a race is doubly-successful in this way, it's very possible that a resource was acquired which will now just disappear. In most cases, resources which are acquired and then later exposed to an error or self-cancelation will trigger finalizers, but often not in success cases, so this is particularly dangerous.
In a very real sense, race should never have returned Either. The more correct return type would have been Ior.
We can address this in one of two ways. We can either introduce a new variant, like raceInclusive, which returns Ior and then deprecate race. Alternatively, we can play tricks with Dummy implicits and break everyone's sources:
def race[A, B](fa: F[A], fb: F[B])(implicit D: Dummy): F[Ior[A, B]] = ???
private[effect] def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] = ???
I'm very tempted to say this is better because it avoids significant problems and cleans up the API (in some sense), but this is definitely a request for comments.
This is all orthogonal to the fiber isolation semantics of race. This affects a few things, but the most significant being self-cancelation semantics: do we treat self-cancelation as "losing the race", or do we treat it as cancelation and embed that result into the parent fiber? The former semantic leads to issues like #3396, while the latter semantic opens up some interesting possibilities (such as a computation "giving up" and allowing the alternative to complete naturally).
racePair is capable of handling all of this generality, and so for any use-cases which are not extremely common, we should push people in that direction, despite the fact that it's very low-level and somewhat hard to work with correctly. We should focus the pretty parts of the API on the most common semantics, which is likely fiber non-isolation (i.e. timeout semantics).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked Discord discussion and issue #3396, then review the existing race and racePair APIs. Compare the proposed raceInclusive/Ior approach with the breaking-default alternative, including self-cancellation and fiber-isolation semantics. Done requires a decided API direction and maintainer-approved scope; no files or tests are named.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100