Implements Promise.become(fiber) to address #9877.
The issue identifies that when a Fiber forks work which will eventually complete a Promise, and then awaits that Promise, we pay an unnecessary overhead:
promise.await allocates an asyncInterrupt callback to register itselfPromise.become(fiber) wires a fiber directly to a promise by registering an observer via fiber.unsafe.addObserver. When the fiber completes, the observer fires completeWith(ZIO.done(exit)), completing the promise with zero extra allocation on the completion path.
// Before: indirect, allocates intermediate callback
for {
promise <- Promise.make[E, A]
fiber <- (work >>= promise.succeed).fork
result <- promise.await
} yield result
// After: direct observer registration, no intermediate callback
for {
promise <- Promise.make[E, A]
fiber <- work.fork
_ <- promise.become(fiber)
result <- promise.await
} yield result
// Public API (safe)
def become(fiber: Fiber.Runtime[E, A])(implicit trace: Trace): UIO[Unit] =
ZIO.succeed(unsafe.become(fiber)(Unsafe))
// Unsafe implementation
def become(fiber: Fiber.Runtime[E, A])(implicit unsafe: Unsafe): Unit = {
if (!isDone) {
fiber.unsafe.addObserver { exit =>
completeWith(ZIO.done(exit))
}
}
}
Key properties:
fiber.unsafe.addObserver is designed to handle the race between registration and completion; if the fiber has already completed, the observer fires immediatelycompleteWith uses CAS internally, so multiple observers or concurrent completions are safeisDone check avoids unnecessary observer registration when the promise is already completePromise.scala: Add become to the public API, UnsafeAPI trait, and the concrete AtomicReference-based implementationFixes #9877
/claim #9877
hhhcccbbb
@hhhcccbbb
ZIO
@ZIO