Skip to content

FiberHandle.clear looks racy ? #5906

@tensor2077

Description

@tensor2077

What version of Effect is running?

3.19.11

What steps can reproduce the bug?

See:

restore(Fiber.interruptAs(self.state.fiber, FiberId.combine(fiber.id(), internalFiberId))),
Effect.sync(() => {
if (self.state._tag === "Open") {
self.state.fiber = undefined
}

export const clear = <A, E>(self: FiberHandle<A, E>): Effect.Effect<void> =>
  Effect.uninterruptibleMask((restore) =>
    Effect.withFiberRuntime((fiber) => {
      if (self.state._tag === "Closed" || self.state.fiber === undefined) {
        return Effect.void
      }

      return Effect.zipRight(
        // This can suspend while waiting for the target fiber to actually terminate,
        // which opens a window for another fiber to call FiberHandle.run/set.
        restore(Fiber.interruptAs(self.state.fiber, FiberId.combine(fiber.id(), internalFiberId))),
        Effect.sync(() => {
          // If another fiber stored a new fiber into the handle while we were waiting above,
          // this unconditional write can wipe out the newer reference.
          if (self.state._tag === "Open") {
            self.state.fiber = undefined
          }
        })
      )
    })
  )

clear interrupts the fiber currently stored in the handle and then waits for it to finish. While it’s waiting, another fiber can call FiberHandle.run / set and put a new fiber into the handle. When clear continues, it blindly does state.fiber = undefined, which can wipe out the newer fiber reference.

So the handle can appear empty (e.g. unsafeGet returns None / get fails) even though the newer fiber is still running, meaning the handle has lost the reference and can no longer cancel/manage that fiber.

perhaps a fix like this:

export const clear = <A, E>(self: FiberHandle<A, E>): Effect.Effect<void> =>
  Effect.uninterruptibleMask((restore) =>
    Effect.withFiberRuntime((runtimeFiber) => {
      if (self.state._tag === "Closed" || self.state.fiber === undefined) {
        return Effect.void
      }

      const target = self.state.fiber

      return Effect.zipRight(
        restore(Fiber.interruptAs(target, FiberId.combine(runtimeFiber.id(), internalFiberId))),
        Effect.sync(() => {
          if (self.state._tag === "Open" && self.state.fiber === target) {
            self.state.fiber = undefined
          }
        })
      )
    })
  )

What is the expected behavior?

No response

What do you see instead?

No response

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions