diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/GenSpawn.scala b/kernel/shared/src/main/scala/cats/effect/kernel/GenSpawn.scala index b678561ce4..0fe82eddff 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/GenSpawn.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/GenSpawn.scala @@ -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 @@ -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) } /** @@ -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