All Downloads are FREE. Search and download functionalities are using the official Maven repository.

korolev.akka.util.KorolevStreamPublisher.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2020 Aleksey Fomkin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package korolev.akka.util

import korolev.akka.util.KorolevStreamPublisher.MultipleSubscribersProhibitedException
import korolev.effect.{Effect, Hub, Reporter, Stream}
import korolev.effect.syntax._
import org.reactivestreams.{Publisher, Subscriber, Subscription}

final class KorolevStreamPublisher[F[_] : Effect, T](stream: Stream[F, T],
                                                     fanout: Boolean) extends Publisher[T] {

  private implicit val reporter: Reporter = Reporter.PrintReporter

  private var hasActualSubscriber = false

  private val hub =
    if (fanout) Hub(stream)
    else null

  private final class Counter {

    @volatile var n: Long = 0
    @volatile var pending: Effect.Promise[Unit] = _
    val res = Right(())

    def value(): F[Long] = Effect[F].delay(n)

    def decOrLock(): F[Unit] = Effect[F].promise[Unit] { cb =>
      this.synchronized { // FIXME use CAS?
        if (n == 0) {
          pending = cb
        } else {
          n -= 1
          cb(res)
        }
      }
    }

    def unsafeSet(x: Long): Unit =
      this.synchronized {
        n = x
        if (x > 0 && pending != null) {
          val cb = pending
          pending = null
          cb(res)
        }
      }
  }

  private final class StreamSubscription(stream: Stream[F, T],
                                         subscriber: Subscriber[_ >: T]) extends Subscription {

    private val counter = new Counter()

    private def loop(): F[Unit] =
      for {
        _ <- counter.decOrLock()
        maybeItem <- stream.pull()
        _ <- maybeItem match {
          case Some(item) =>
            subscriber.onNext(item)
            loop()
          case None =>
            subscriber.onComplete()
            Effect[F].unit
        }
      } yield ()

    loop().runAsync {
      case Left(error) => subscriber.onError(error)
      case Right(_) => ()
    }

    def request(n: Long): Unit = {
      counter.unsafeSet(n)
    }

    def cancel(): Unit = {
      stream
        .cancel()
        .runAsyncForget
    }
  }

  def subscribe(subscriber: Subscriber[_ >: T]): Unit = {
    if (hub != null) {
      hub.newStream().runAsyncSuccess { newStream =>
        val subscription = new StreamSubscription(newStream, subscriber)
        subscriber.onSubscribe(subscription)
      }
    } else {
      if (hasActualSubscriber)
        throw MultipleSubscribersProhibitedException()
      subscriber.onSubscribe(new StreamSubscription(stream, subscriber))
    }
    hasActualSubscriber = true
  }
}

object KorolevStreamPublisher {
  final case class MultipleSubscribersProhibitedException()
    extends Exception("Multiple subscribers prohibited for this KorolevStreamPublisher")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy