Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZIO Core: Disconnect Children in Uninterruptible Race #3125

Merged
merged 6 commits into from
Mar 22, 2020
Merged

ZIO Core: Disconnect Children in Uninterruptible Race #3125

merged 6 commits into from
Mar 22, 2020

Conversation

adamgfraser
Copy link
Contributor

Automatically disconnects children if race is performed in an uninterruptible region. I think there was a bug with the prior implementation of disconnect:

for {
  id    <- ZIO.fiberId
  fiber <- restore(self).forkDaemon
  a     <- restore(fiber.join).onInterrupt(fiber.interruptAs(id).forkDaemon)
} yield a

The problem is that we call restore on fiber.join, which means if we are in an uninterruptible region joining the fiber is itself uninterruptible so so we suspend when we attempt to interrupt the join until the child fiber completes executing (possibly never). I think we want to change that line to be fiber.join.interruptible. This leaves the underlying effect uninterruptible if it was in an uninterruptible region but allows us to continue with the effect in the background.

core/shared/src/main/scala/zio/ZIO.scala Outdated Show resolved Hide resolved
core/shared/src/main/scala/zio/ZIO.scala Outdated Show resolved Hide resolved
core/shared/src/main/scala/zio/ZIO.scala Outdated Show resolved Hide resolved
@adamgfraser
Copy link
Contributor Author

Updated to just disconnect the children in race and leave disconnect unchanged.

neko-kai
neko-kai previously approved these changes Mar 14, 2020
@jdegoes
Copy link
Member

jdegoes commented Mar 14, 2020 via email

@adamgfraser
Copy link
Contributor Author

@jdegoes Will not do anything without your approval!

@jdegoes
Copy link
Member

jdegoes commented Mar 19, 2020

I think this problem should be solved by modifying:

final class InterruptStatusRestore(private val flag: zio.InterruptStatus) extends AnyVal {
  def apply[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] =
    zio.interruptStatus(flag)

  def force[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] = 
    if (flag == InterruptStatus.Uninterruptible) zio.uninterruptible.disconnect.interruptible
    else zio.interruptStatus(flag)
}

Then you can use:

ZIO.uninterruptibleMask { interruptible =>
  ... interruptible.force(zio) ...
}

Other comments inline.

val parentFiberId = descriptor.id
def maybeDisconnect[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] =
if (descriptor.interruptStatus.isInterruptible) zio
else zio.uninterruptible.disconnect.interruptible
Copy link
Member

Choose a reason for hiding this comment

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

Why zio.uninterruptible.disconnect.interruptible? It should be sufficient (and more performant) to do:

zio.disconnect.interruptible

Copy link
Member

Choose a reason for hiding this comment

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

@jdegoes So that the underlying zio itself remains uninterruptible and can't be sent interrupts to - otherwise it could still be interrupted indirectly by supervision etc.

Copy link
Member

Choose a reason for hiding this comment

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

When it's disconnected there's no way to interrupt it aside from having a reference to the fiber.

Copy link
Member

@neko-kai neko-kai Mar 19, 2020

Choose a reason for hiding this comment

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

@jdegoes
It will be interrupted inside disconnect:

  final def disconnect: ZIO[R, E, A] =
    ZIO.uninterruptibleMask(restore =>
      for {
        id    <- ZIO.fiberId
        fiber <- restore(self).forkDaemon
        a     <- restore(fiber.join).onInterrupt(fiber.interruptAs(id).forkDaemon)
      } yield a
    )

With zio.disconnect.interruptible the following will print "interrupted uninterruptible never":

ZIO.sleep(5.seconds).race(ZIO.never.onInterrupt(putStrLn("interrupted uninterruptible never")))

Example
Example 2: No message with uninterruptible

Because the right hand side will be interrupted in restore(fiber.join).onInterrupt(fiber.interruptAs(id).forkDaemon), i.e. omitting zio.uninterruptible makes all races interruptible as in RC17 – which I'm probably all for, but doesn't seem like what you intend.

Besides: all root fiber references are exposed publicly in Fiber.roots AND the interrupt flag can alter logic besides disallowing interrupts (it does so right here) – so it would still be much more sound to force the uninterruptible flag even if it didn't affect external interrupts (which it does)

Copy link
Member

Choose a reason for hiding this comment

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

Ah right, sorry, I should know that. 😆

@@ -3289,6 +3290,10 @@ object ZIO extends ZIOCompanionPlatformSpecific {
final class InterruptStatusRestore(private val flag: zio.InterruptStatus) extends AnyVal {
def apply[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] =
zio.interruptStatus(flag)

def force[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] =
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def force[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] =
/**
* Returns a new effect that, if the parent region is uninterruptible, can be interrupted in the background
* instantaneously. If the parent region is interruptible, then the effect can be interrupted normally,
* in the foreground.
*/
def force[R, E, A](zio: ZIO[R, E, A]): ZIO[R, E, A] =

jdegoes
jdegoes previously approved these changes Mar 22, 2020
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.

3 participants