Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 83 additions & 29 deletions kernel/shared/src/main/scala/cats/effect/kernel/GenSpawn.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,37 @@ trait GenSpawn[F[_], E] extends MonadCancel[F, E] with Unique[F] {
def racePair[A, B](fa: F[A], fb: F[B])
: F[Either[(Outcome[F, E, A], Fiber[F, E, B]), (Fiber[F, E, A], Outcome[F, E, B])]]

/**
* Races the evaluation of two fibers, cancels the loser, and returns the [[Outcome]] of both.
* If the race is canceled before one or both participants complete, then then whichever ones
* are incomplete are canceled.
*
* @param fa
* the effect for the first racing fiber
* @param fb
* the effect for the second racing fiber
*/
def raceOutcomeBoth[A, B](fa: F[A], fb: F[B])
: F[Either[(Outcome[F, E, A], Outcome[F, E, B]), (Outcome[F, E, A], Outcome[F, E, B])]] =
uncancelable { poll =>
poll(racePair(fa, fb)).flatMap {
case Left((oca, f)) =>
val joined =
if (oca.isCanceled)
poll(f.join)
else
f.cancel *> f.join
joined.map(ocb => Left((oca, ocb)))
case Right((f, ocb)) =>
val joined =
if (ocb.isCanceled)
poll(f.join)
else
f.cancel *> f.join
joined.map(oca => Right((oca, ocb)))
}
}

/**
* Races the evaluation of two fibers that returns the [[Outcome]] of the winner. The winner
* of the race is considered to be the first fiber that completes with an outcome. The loser
Expand All @@ -314,11 +345,9 @@ trait GenSpawn[F[_], E] extends MonadCancel[F, E] with Unique[F] {
* [[race]] for a simpler variant that returns the successful outcome.
*/
def raceOutcome[A, B](fa: F[A], fb: F[B]): F[Either[Outcome[F, E, A], Outcome[F, E, B]]] =
uncancelable { poll =>
poll(racePair(fa, fb)).flatMap {
case Left((oc, f)) => f.cancel.as(Left(oc))
case Right((f, oc)) => f.cancel.as(Right(oc))
}
raceOutcomeBoth(fa, fb).map {
case Left((oc, _)) => Left(oc)
case Right((_, oc)) => Right(oc)
}

/**
Expand All @@ -345,33 +374,58 @@ trait GenSpawn[F[_], E] extends MonadCancel[F, E] with Unique[F] {
* [[raceOutcome]] for a variant that returns the outcome of the winner.
*/
def race[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] =
uncancelable { poll =>
poll(racePair(fa, fb)).flatMap {
case Left((oc, f)) =>
oc match {
case Outcome.Succeeded(fa) => f.cancel *> fa.map(Left(_))
case Outcome.Errored(ea) => f.cancel *> raiseError(ea)
case Outcome.Canceled() =>
poll(f.join).onCancel(f.cancel).flatMap {
case Outcome.Succeeded(fb) => fb.map(Right(_))
case Outcome.Errored(eb) => raiseError(eb)
case Outcome.Canceled() => poll(canceled) *> never
}
}
case Right((f, oc)) =>
oc match {
case Outcome.Succeeded(fb) => f.cancel *> fb.map(Right(_))
case Outcome.Errored(eb) => f.cancel *> raiseError(eb)
case Outcome.Canceled() =>
poll(f.join).onCancel(f.cancel).flatMap {
case Outcome.Succeeded(fa) => fa.map(Left(_))
case Outcome.Errored(ea) => raiseError(ea)
case Outcome.Canceled() => poll(canceled) *> never
}
}
raceOutcomeBoth(fa, fb).flatMap(embedRaceOutcome(_))

/**
* Like [[race]], but in the case that neither fiber completes with [[Outcome.Canceled]],
* priority is given to the outcome of the second fiber. Hence, this method is right-biased.
*
* @param fa
* the effect for the first racing fiber
* @param fb
* the effect for the second racing fiber
*
* @see
* [[race]] for a non-biased variant
*/
def raceBiased[A, B](fa: F[A], fb: F[B]): F[Either[A, B]] =
raceOutcomeBoth(fa, fb).flatMap { raceOutcome =>
raceOutcome.merge._2 match {
case Outcome.Succeeded(fb) => fb.map(Right(_))
case Outcome.Errored(eb) => raiseError(eb)
case Outcome.Canceled() => embedRaceOutcome(raceOutcome)
}
}

private[this] def embedRaceOutcome[A, B](
raceOutcome: Either[
(Outcome[F, E, A], Outcome[F, E, B]),
(Outcome[F, E, A], Outcome[F, E, B])]): F[Either[A, B]] =
raceOutcome match {
case Left((oca, ocb)) =>
oca match {
case Outcome.Succeeded(fa) => fa.map(Left(_))
case Outcome.Errored(ea) => raiseError(ea)
case Outcome.Canceled() =>
ocb match {
case Outcome.Succeeded(fb) => fb.map(Right(_))
case Outcome.Errored(eb) => raiseError(eb)
case Outcome.Canceled() => canceled *> never
}
}
case Right((oca, ocb)) =>
ocb match {
case Outcome.Succeeded(fb) => fb.map(Right(_))
case Outcome.Errored(eb) => raiseError(eb)
case Outcome.Canceled() =>
oca match {
case Outcome.Succeeded(fa) => fa.map(Left(_))
case Outcome.Errored(ea) => raiseError(ea)
case Outcome.Canceled() => canceled *> never
}
}
}

/**
* Races the evaluation of two fibers and returns the [[Outcome]] of both. If the race is
* canceled before one or both participants complete, then then whichever ones are incomplete
Expand Down