/fixes #9877 /claim #9877
A Promise awaiting completion is essentially a Fiber parked awaiting an async callback. When a Fiber forks work that will eventually complete a promise, then awaits that promise, we end up with an extra fiber + flatMap for the bridging:
fiber.await.flatMap(exit => promise.done(exit)).fork *> promise.await
This creates unnecessary allocations and indirection — the promise and fiber are doing the same job independently.
Promise.become(fiber) wires a fiber’s completion directly to a promise at the unsafe level using fiber.unsafe.addObserver. The observer callback calls completeWith on the promise when the fiber exits, so there’s no intermediate ZIO value or extra fiber involved:
// Before (traditional pattern):
fiber.await.flatMap(exit => promise.done(exit)).fork *> promise.await
// After:
promise.become(fiber) *> promise.await
The implementation is ~20 lines in Promise.scala:
def become(fiber: Fiber.Runtime[E, A]): UIO[Boolean] — returns whether the link was established (promise was still pending)Done (returns false), otherwise attaches the observer (returns true)JMH benchmark comparing the full pattern (create promise → fork fiber → bridge → await) repeated n times:
Benchmark (n) Mode Cnt Score Error Units
PromiseBecomeBenchmark.promiseBecomeForkAwait 1000 thrpt 5 1863.917 ± 199.008 ops/s
PromiseBecomeBenchmark.promiseBecomeForkAwait 10000 thrpt 5 192.390 ± 43.869 ops/s
PromiseBecomeBenchmark.promiseBecomeForkAwait 100000 thrpt 5 14.736 ± 0.898 ops/s
PromiseBecomeBenchmark.traditionalForkAwaitPromise 1000 thrpt 5 681.232 ± 377.856 ops/s
PromiseBecomeBenchmark.traditionalForkAwaitPromise 10000 thrpt 5 96.271 ± 32.285 ops/s
PromiseBecomeBenchmark.traditionalForkAwaitPromise 100000 thrpt 5 4.774 ± 4.860 ops/s
| n | Traditional | Promise.become | Improvement |
|---|---|---|---|
| 1,000 | 681 ops/s | 1,864 ops/s | +174% |
| 10,000 | 96 ops/s | 192 ops/s | +100% |
| 100,000 | 4.8 ops/s | 14.7 ops/s | +209% |
The improvement holds (and actually grows) under load because become avoids creating the bridging fiber entirely.
To reproduce: sbt 'benchmarks/Jmh/run PromiseBecomeBenchmark'
Four cases added to PromiseSpec:
become completes promise with fiber success — happy pathbecome completes promise with fiber failure — error propagationbecome returns false for already completed promise — idempotencybecome with already completed fiber — eager completioncore/shared/src/main/scala/zio/Promise.scala — become + unsafe implcore-tests/shared/src/test/scala/zio/PromiseSpec.scala — 4 testsbenchmarks/src/main/scala/zio/PromiseBecomeBenchmark.scala — JMH benchmarkMiguel Lemos
@miguelemosreverte
ZIO
@ZIO