-
Notifications
You must be signed in to change notification settings - Fork 26
add zioToPublisher conversion #326
base: series/2.x
Are you sure you want to change the base?
Conversation
This is awesome. Thanks for this contribution! Just one small question. |
Thanks for the positive feedback. I consider that PR to be in draft. I would like to implement subscription cancellation using fiber interruption. However, something does not yet fit. A corresponding unit test does not terminate and I struggle with debugging that situation. |
@swachter OK. Let me know if you'd like me to take a look. |
@swachter when I do this change: o <- zio
.tapError(e => ZIO.succeed(subscriber.onError(e)))
.onInterrupt(
ZIO.succeed(subscriber.onError(new RuntimeException("Fiber interrupted.")))
) <& subscription.offer(1) the following test succeeds: test("interrupts subscription on fiber interruption") {
for {
promise <- Promise.make[Nothing, Unit]
publisher <- promise.await.toPublisher
resultFiber <- publisher.toZIOStream().runDrain.exit.fork
_ <- promise.interrupt
result <- resultFiber.join
} yield assert(result)(fails(anything))
} @@ TestAspect.timeout(1.second) Is that what you had in mind? Note that |
I pushed my current implementation, altough it is not yet working. The main idea is in line Adapters.scala#L414 where subscription cancellation is implemented by fiber interruption. There seems to be some issue with fiber interruption, though. The following test case does not work:
|
AFAIS Fiber interruption on subscription cancellation would be the main benefit of the new implementation. It would interrupt ongoing calculations if their result is no more of interest. |
I think I found a bug in ZIO 2.0.0-RC6 (cf. ZIO#6888). That bug prevents that interruption works as expected. I changed the implementation slightly by replacing the use of The PR should be ready for review now. |
Oh, now I understand what you want to achieve. I assumed you wanted to signal cancellation to the subscriber when the original ZIO is cancelled. |
I would like to propose a slightly different implementation, which uses def zioToPublisher[R, E <: Throwable, O](
zio: => ZIO[R, E, O]
)(implicit trace: Trace): URIO[R, Publisher[O]] =
ZIO.runtime.map { runtime => subscriber =>
if (subscriber == null) {
throw new NullPointerException("Subscriber must not be null.")
} else {
val cancellationHookRef = new AtomicReference[FiberId => Exit[Any, Unit]]
val subscription = new Subscription {
override def cancel(): Unit = {
val hook = cancellationHookRef.compareAndExchange(null, _ => Exit.interrupt(FiberId.None))
if (hook != null) {
hook(FiberId.None)
()
}
}
override def request(n: Long): Unit =
if (n <= 0) throw new RuntimeException("demand must be > 0")
else {
if (cancellationHookRef.get == null) {
val latch = Promise.unsafeMake[Unit, Unit](FiberId.None)
val hook =
runtime.unsafeRunAsyncCancelable(
latch.await *>
zio
.foldZIO(
e => ZIO.succeed(subscriber.onError(e)),
a => ZIO.succeed(subscriber.onNext(a)) *> ZIO.succeed(subscriber.onComplete())
)
)(_ => ())
if (cancellationHookRef.compareAndSet(null, hook)) latch.unsafeDone(ZIO.succeedNow(()))
else latch.unsafeDone(ZIO.fail(()))
}
}
}
subscriber.onSubscribe(subscription)
}
} WDYT? |
I pushed your alternative implementation of Some thoughts:
I think I need more time to digest these things. |
…terruptionException" still fails
This PR adds the conversion
zioToPublisher
.