Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@regiskuckaertz
Copy link
Member

Rewrites IO.bracket0 following @jdegoes 's description here.

You need to fork inside the uninterruptible section, right after setting the ref containing the resource. Forking can't fail so it's safe to do that; you return the fiber from the uninterruptible section. Then outside the interruptible section, you join on the forked use. Finally, you return that value, all wrapped in ensuring to make sure that if acquire succeeds (hence, necessarily: the ref is set, and use is forked), there will be an exit value.

It's not quite there yet but I'm getting closer.

Ref[Option[A]](None).flatMap { m =>
for {
f <- acquire.flatMap(a => m.set(Some(a)) *> use(a).fork).uninterruptibly
b <- f.join.ensuring(m.get.flatMap(_.fold(IO.unit)(a => f.observe.flatMap(r => release(a, r)))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ensuring needs to go around the outermost block. Not around join, because if it's around join, then if join does not start executing (it's possible—the action can be interrupted right after the acquire block completes), then the finalizer will not execute. So move the ensuring to the outside of the for loop. This implies you need to put the fiber in the Ref, too.

Maybe something like:

    Ref[Option[(A, Either[Fiber[E, A], B])]](None).flatMap { m =>
      (for {
        f <- acquire.flatMap(a => use(a).fork.flatMap(f => m.set(Some(a -> Left(f)) *> IO.now(f)))).uninterruptibly
        b <- f.join
        _ <- m.update(_.map(t => (t._1, Right(b)))) // We have the `b`, so we don't need the fiber anymore
      } yield b).ensuring {
        m.get.flatMap(_.fold(IO.unit) {
          case (a, Left(fiber)) => fiber.observe.flatMap(release(a, _))
          case (a, Right(b))    => release(a, ExitResult.Completed(b))
        })
      }
    }

This is not quite correct. If you add supervised it won't really help.

We need something like tryObserve or isDone added to Fiber, so we can check to see if the fiber is done at the moment when the ensuring finalizer is being executed. If the fiber is not done, it means the main fiber is being interrupted, so we should interrupt the child fiber. If the fiber is done, then it means we should observe and use the observed exit result on the release action.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😮 that's brilliant! Do we need the Left/Right ceremony? A tryObserve should be super fast, synchronous even. I've submitted a new version to let you see; will work on tryObserve now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, maybe you can eliminate it. The important thing is to pass the b to release if use succeeds.

@regiskuckaertz
Copy link
Member Author

This now works 🎉 Thank you for your comment

Copy link
Member

@jdegoes jdegoes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! ❤️ Just a few minor cleanups and it should be good to go. Awesome work! 💪

}
self.observe.seqWith(that.observe)(combineExitResults)

def tryObserve: IO[Nothing, Option[ExitResult[E1, C]]] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest self.tryObserve.seqWith(that.tryObserve)(???)

def interrupt0(ts: List[Throwable]): IO[Nothing, Unit] =
self.interrupt0(ts) *> that.interrupt0(ts)

private def combineExitResults: (ExitResult[E, A], ExitResult[E1, B]) => ExitResult[E1, C] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about moving this method to ExitResult, and calling it zipWith? e.g. e1.zipWith(e2)(f(_, _))?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Consistent vocabulary and behaviour across the whole API ❤️

f.tryObserve.flatMap {
case Some(r) => release(a, r)
case None => f.interrupt
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct to me! (Of course I may have said that before. 😆)

Getting information out of the use action while still preserving its interruptibility is amazingly difficult with only ensuring and uninterruptible as primitives. At least the more common version of bracket is going to be much faster (IO.bracket), and it looks correct to me, too (using the same strategy, but no need to fork).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least the more common version of bracket is going to be much faster

I still don't have a good intuition of the performance impact of using one version vs another, I mean:

  • a fiber is just a few hundred bytes, forking must be super fast
  • joining has virtually no cost since it merely adds a callback to a list
  • tryObserve (should we call it poll? like Promise.poll) is super fast

If my reasoning is correct, that leaves interruption, which may block the thread indeed.

}
def tryObserve: IO[Nothing, Option[ExitResult[Throwable, A]]] = IO.sync {
ftr.value match {
case Some(Success(a)) => Some(ExitResult.Completed(a))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can simplify with ftr.value.map { case Success(a) ... case Failure(t) ... }.

@jdegoes jdegoes merged commit 43f71bd into zio:master Sep 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants