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

io.reactors.protocol.reliable-protocols.scala Maven / Gradle / Ivy

The newest version!
package io.reactors
package protocol



import io.reactors.common.BinaryHeap
import io.reactors.common.UnrolledRing
import io.reactors.services.Channels



/** Reliable communication protocols ensure reliable delivery.
 *
 *  Reliable delivery is normally ensured when delivering events between reactors
 *  in the same reactor system. However, when the underlying transport is an unreliable
 *  network, events may be delayed, lost, duplicated or arbitrarily corrupted,
 *  depending on the properties of the communication medium.
 *
 *  Reliability protocols ensure that all original events are delivered exactly once
 *  and in order, using either one-way or two-way reliable links.
 */
trait ReliableProtocols {
  /** Represents a connected reliable channel.
   *
   *  When using a reliable channel to send events, clients have some level of guarantee
   *  that their events will not be impaired in some way. The exact guarantees are
   *  detailed in the `Policy` object which must be specified when establishing a
   *  reliable link.
   *
   *  To close this reliable link, clients must use the associated subscription.
   *
   *  @tparam T                 type of the events sent by this reliable channel
   *  @param channel            channel underlying the reliable link
   *  @param subscription       subscription associated with the reliable link
   */
  case class Reliable[T](channel: Channel[T], subscription: Subscription)

  object Reliable {
    /** Represents the reading side of a reliable link.
     *
     *  @tparam T               type of the events incoming on this reliable link
     *  @param events           event stream for incoming events of this link
     *  @param subscription     subscription of the link
     */
    case class Link[T](events: Events[T], subscription: Subscription)
    extends io.reactors.protocol.Connection[T]

    /** State of the reliable channel server.
     *
     *  @tparam T               type of the events in the incoming links
     *  @param channel          channel used to request reliable links
     *  @param links            event stream of established links
     *  @param subscription     subscription of the server
     */
    case class Server[T](
      channel: Channel[Req[T]],
      links: Events[Reliable.Link[T]],
      subscription: Subscription
    ) extends ServerSide[Req[T], Reliable.Link[T]]

    /** Type of the requests for establishing reliable channels.
     *
     *  @tparam T               type of events propagated through reliable channels
     */
    type Req[T] = io.reactors.protocol.TwoWay.Req[Long, Stamp[T]]

    /** Captures the assumptions about the lossiness of the underlying transport.
     *
     *  Depending on the lossiness assumptions, different policies must be used
     *  to establish reliability.
     */
    trait Policy {
      /** Wires client-sent events to the two-way link.
       */
      def client[T: Arrayable](
        sends: Events[T],
        twoWay: io.reactors.protocol.TwoWay[Long, Stamp[T]]
      ): Subscription

      /** Wires the two-way link to the delivery channel of the server.
       */
      def server[T: Arrayable](
        twoWay: io.reactors.protocol.TwoWay[Stamp[T], Long],
        deliver: Channel[T]
      ): Subscription
    }

    object Policy {
      /** Assumes that the transport may reorder and indefinitely delay some events.
       *
       *  Furthermore, the requirement is that the underlying medium is not lossy,
       *  and that it does not create duplicates.
       *
       *  @param window     number of events that the policy will send without an
       *                    acknowledgement, before starting to buffer events locally
       */
      def reorder(window: Int) = new Reorder(window)

      /** See `reorder`.
       */
      class Reorder(val window: Int) extends Reliable.Policy {
        def client[T: Arrayable](
          sends: Events[T],
          twoWay: io.reactors.protocol.TwoWay[Long, Stamp[T]]
        ): Subscription = {
          var lastAck = 0L
          var lastStamp = 0L
          val queue = new UnrolledRing[T]
          val io.reactors.protocol.TwoWay(channel, acks, subscription) = twoWay
          sends onEvent { x =>
            if ((lastStamp - lastAck) < window) {
              lastStamp += 1
              channel ! Stamp.Some(x, lastStamp)
            } else {
              queue.enqueue(x)
            }
          }
          acks onEvent { stamp =>
            lastAck = math.max(lastAck, stamp)
            while (queue.nonEmpty && (lastStamp - lastAck) < window) {
              lastStamp += 1
              channel ! Stamp.Some(queue.dequeue(), lastStamp)
            }
          } andThen (channel ! Stamp.None())
        }

        def server[T: Arrayable](
          twoWay: io.reactors.protocol.TwoWay[Stamp[T], Long],
          deliver: Channel[T]
        ): Subscription = {
          val io.reactors.protocol.TwoWay(acks, events, subscription) = twoWay
          var nextStamp = 1L
          val queue = new BinaryHeap[Stamp[T]]()(
            implicitly,
            Order((x, y) => (x.stamp - y.stamp).toInt)
          )
          events onMatch {
            case stamp @ Stamp.Some(x, timestamp) =>
              if (timestamp == nextStamp) {
                acks ! nextStamp
                nextStamp += 1
                deliver ! x
                while (queue.nonEmpty && queue.head.stamp == nextStamp) {
                  val Stamp.Some(y, _) = queue.dequeue()
                  acks ! nextStamp
                  nextStamp += 1
                  deliver ! y
                }
              } else {
                queue.enqueue(stamp)
              }
          } andThen (acks ! -1)
        }
      }
    }

    object TwoWay {
      /** State of the reliable two-way channel server.
       *
       * @tparam I               incoming event type, from the client's perspective
       * @tparam O               outgoing event type, from the client's perspective
       * @param channel          channel used to establish a reliable two-way link
       * @param links            event stream that emits established links
       * @param subscription     subscription of the server
       */
      case class Server[I, O](
        channel: io.reactors.protocol.Server[
          Channel[Reliable.Req[I]],
          Channel[Reliable.Req[O]]
        ],
        links: Events[TwoWay[O, I]],
        subscription: Subscription
      ) extends ServerSide[TwoWay.Req[I, O], TwoWay[O, I]]

      /** Type of the request used to establish reliable two-way links.
       *
       *  @tparam I          type of the incoming events, from the client's perspective
       *  @tparam O          type of the outgoing events, from the client's perspective
       */
      type Req[I, O] = io.reactors.protocol.Server.Req[
        Channel[Reliable.Req[I]],
        Channel[Reliable.Req[O]]
      ]

      /** Captures the assumption about the lossiness of the underlying transport.
       *
       *  Depending on the lossiness of the transport, different policies must be
       *  used when establishing a reliable two-way link.
       */
      trait Policy extends Reliable.Policy {
        /** Guard placed on the input server after it is created.
         *
         *  This can be used to, for example, add a timeout before closing the server.
         */
        def inputServerGuard[I](inServer: Reliable.Server[I]): Subscription

        /** Guard placed on the output server after it is created.
         *
         *  This can be used to, for example, add a timeout before closing the server.
         */
        def outputServerGuard[O](outServer: Reliable.Server[O]): Subscription
      }

      object Policy {
        /** Policy that assumes that the transport may arbitrarily delay any event.
         *
         *  Additionally, this policy assumes that the transport may reorder events,
         *  but will never drop, duplicate or corrupt the events.
         */
        def reorder(window: Int) =
          new Reliable.Policy.Reorder(window) with Reliable.TwoWay.Policy {
            def inputServerGuard[I](inServer: Reliable.Server[I]) = Subscription.empty
            def outputServerGuard[I](outServer: Reliable.Server[I]) = Subscription.empty
          }
      }
    }

    /** Tags the channel of the `Reliable` object.
     */
    object ChannelTag extends Channels.Tag
  }

  /* One-way reliable protocols */

  implicit class ReliableChannelBuilderOps(val builder: ChannelBuilder) {
    /** Opens a connector for a reliable link server.
     *
     *  Note that opening a connector does not yet start the protocol.
     *  To start the reliable channel server, `serveReliable` must be called
     *  on the connector.
     *
     *  @tparam T        type of the events of the established reliable links
     *  @return          a connector for the reliable link server
     */
    def reliableServer[T]: Connector[Reliable.Req[T]] = {
      builder.open[Reliable.Req[T]]
    }
  }

  implicit class ReliableConnectorOps[T: Arrayable](
    val connector: Connector[Reliable.Req[T]]
  ) {
    /** Starts a server that responds to incoming requests for reliable links.
     *
     *  @param policy     captures assumptions about the underlying transport
     *  @return           state of the new reliable links server
     */
    def serveReliable(
      policy: Reliable.Policy = Reliable.Policy.reorder(128)
    ): Reliable.Server[T] = {
      val system = Reactor.self.system
      val twoWayServer = connector.serveTwoWay()
      val links = twoWayServer.links map {
        case twoWay @ TwoWay(_, events, _) =>
          val reliable =
            system.channels.template(Reliable.ChannelTag).daemon.shortcut.open[T]
          val resources = policy.server(twoWay, reliable.channel)
          val subscription = Subscription(reliable.seal())
            .chain(resources)
            .chain(twoWay.subscription)
          events.collect({ case s @ Stamp.None() => s })
            .toIVar.on(subscription.unsubscribe())
          Reliable.Link(reliable.events, subscription)
      } toEmpty

      Reliable.Server(
        connector.channel,
        links,
        links.chain(twoWayServer.subscription).andThen(connector.seal())
      )
    }
  }

  implicit class ReliableServerOps[T: Arrayable](
    val server: Channel[Reliable.Req[T]]
  ) {
    /** Opens a reliable link.
     *
     *  @param policy      captures assumption about the underlying transport
     *  @return            single-assignment variable that is eventually completed with
     *                     the reliable link object
     */
    def openReliable(
      policy: Reliable.Policy = Reliable.Policy.reorder(128)
    ): IVar[Reliable[T]] = {
      val system = Reactor.self.system
      server.connect() map {
        case twoWay @ TwoWay(_, acks, _) =>
          val reliable = system.channels.daemon.shortcut.open[T]
          val resources = policy.client(reliable.events, twoWay)
          val subscription = Subscription(reliable.seal())
            .chain(resources)
            .chain(twoWay.subscription)
          acks.filter(_ == -1).toIVar.on(subscription.unsubscribe())
          Reliable(reliable.channel, subscription)
      } toIVar
    }
  }

  implicit class ReliableReactorCompanionOps(val reactor: Reactor.type) {
    /** Same as calling the `serveReliable` operation, but creates a `Proto` object.
     */
    def reliableServer[T: Arrayable](policy: Reliable.Policy)(
      f: (Reliable.Server[T], Reliable.Link[T]) => Unit
    ): Proto[Reactor[Reliable.Req[T]]] = {
      Reactor[Reliable.Req[T]] { self =>
        val server = self.main.serveReliable(policy)
        server.links.onEvent(link => f(server, link))
      }
    }
  }

  implicit class ReliableSystemOps(val system: ReactorSystem) {
    /** Same as calling the `serveReliable` operation, but starts the reactor directly.
     */
    def reliableServer[T: Arrayable](policy: Reliable.Policy)(
      f: (Reliable.Server[T], Reliable.Link[T]) => Unit
    ): Channel[Reliable.Req[T]] = {
      system.spawn(Reactor.reliableServer[T](policy)(f))
    }
  }

  /* Two-way reliable protocols */

  implicit class ReliableTwoWayChannelBuilderOps(val builder: ChannelBuilder) {
    /** Opens a connector that can accept requests for reliable two-way links.
     *
     *  Does not start the reliable two-way server protocol. For that, clients must
     *  call the `serverTwoWayReliable` method on the connector.
     *
     *  @tparam I      type of the incoming events, from the client's perspective
     *  @tparam O      type of the outgoing events, from the client's perspective
     *  @return        a newly created connector for the reliable two-way requests
     */
    def reliableTwoWayServer[I, O]: Connector[Reliable.TwoWay.Req[I, O]] = {
      builder.open[Reliable.TwoWay.Req[I, O]]
    }
  }

  implicit class ReliableTwoWayConnectorOps[I: Arrayable, O: Arrayable](
    val connector: Connector[Reliable.TwoWay.Req[I, O]]
  ) {
    /** Starts a server that accepts requests for two-way reliable links.
     *
     *  @param policy  captures assumptions about the underlying transport
     *  @return        state of the newly created two-way reliable server
     */
    def serveTwoWayReliable(
      policy: Reliable.TwoWay.Policy = Reliable.TwoWay.Policy.reorder(128)
    ): Reliable.TwoWay.Server[I, O] = {
      val system = Reactor.self.system
      val links = connector.events.map {
        case req @ (inServer, reply) =>
          val outServer = system.channels.daemon.shortcut.reliableServer[O]
            .serveReliable(policy)
          outServer.links.on(outServer.subscription.unsubscribe())
          policy.outputServerGuard(outServer)
          reply ! outServer.channel

          val inReliable = inServer.openReliable(policy)

          (outServer.links sync inReliable) { (out, in) =>
            TwoWay(in.channel, out.events, in.subscription.chain(out.subscription))
          } toIVar
      }.union.toEmpty

      Reliable.TwoWay.Server(
        connector.channel,
        links,
        links.andThen(connector.seal())
      )
    }
  }

  implicit class ReliableTwoWayServerOps[I: Arrayable, O: Arrayable](
    val reliableServer: Channel[io.reactors.protocol.Reliable.TwoWay.Req[I, O]]
  ) {
    /** Requests and establishes a two-way reliable link.
     *
     *  @param policy    captures assumptions about the underlying transport
     *  @return          a single-assignment variable with the established two-way
     *                   link, completed once the link is established
     */
    def connectReliable(
      policy: Reliable.TwoWay.Policy = Reliable.TwoWay.Policy.reorder(128)
    ): IVar[TwoWay[I, O]] = {
      val system = Reactor.self.system
      val inServer = system.channels.daemon.shortcut.reliableServer[I]
        .serveReliable(policy)
      policy.inputServerGuard(inServer)
      inServer.links.on(inServer.subscription.unsubscribe())

      (reliableServer ? inServer.channel).map { outServer =>
        val outReliable = outServer.openReliable(policy)

        (outReliable sync inServer.links) { (out, in) =>
          TwoWay(out.channel, in.events, out.subscription.chain(in.subscription))
        }
      }.union.toIVar
    }
  }

  implicit class ReliableTwoWayReactorCompanionOps(val reactor: Reactor.type) {
    /** Same as `serveTwoWayReliable`, but creates a `Proto` object.
     *
     *  The reactor's main channel is used as the two-way reliable link server.
     */
    def reliableTwoWayServer[I: Arrayable, O: Arrayable](
      policy: Reliable.TwoWay.Policy
    )(
      f: (Reliable.TwoWay.Server[I, O], TwoWay[O, I]) => Unit
    ): Proto[Reactor[Reliable.TwoWay.Req[I, O]]] = {
      Reactor[Reliable.TwoWay.Req[I, O]] { self =>
        val server = self.main.serveTwoWayReliable(policy)
        server.links.onEvent(twoWay => f(server, twoWay))
      }
    }
  }

  implicit class ReliableTwoWaySystemOps[I: Arrayable, O: Arrayable](
    val system: ReactorSystem
  ) {
    /** Same as `serveTwoWayReliable`, but immediately starts the reactor.
     *
     *  The reactor's main channel is used as the two-way reliable link server.
     */
    def reliableTwoWayServer(policy: Reliable.TwoWay.Policy)(
      f: (Reliable.TwoWay.Server[I, O], TwoWay[O, I]) => Unit
    ): Channel[Reliable.TwoWay.Req[I, O]] = {
      val proto = Reactor.reliableTwoWayServer(policy)(f)
      system.spawn(proto)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy