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

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

The newest version!
package io.reactors
package protocol



import scala.collection._
import scala.util.Random



/** Communication patterns for routing.
 */
trait RouterProtocols {
  self: Patterns =>

  implicit class RouterChannelBuilderOps(val builder: ChannelBuilder) {
    /** Creates a new channel with the router signature.
     */
    def router[@spec(Int, Long, Double) T: Arrayable]: Connector[T] = builder.open[T]
  }

  implicit class RouterConnectorOps[@spec(Int, Long, Double) T](
    val conn: Connector[T]
  ) {
    /** Installs routing logic to the router connector.
     *
     *  The router channel routes incoming events to some channel, defined by the
     *  `selector` function.
     *
     *  @tparam T        the type of the routed events
     *  @param selector  function that selects a channel for the given event
     *  @return          a connector for the router channel
     */
    def route(selector: T => Channel[T]): Connector[T] = {
      conn.events.onEvent { x =>
        selector(x) ! x
      }
      conn
    }
  }
}


/** Contains types and factory functions for router protocols.
 */
object Router {
  /** Type of a function that selects a channel given an event.
   */
  type Selector[T] = T => Channel[T]

  /** Always returns a zero channel, which loses all the events sent to it.
   *
   *  @tparam T       type of the events to route
   *  @return         a selector function that drops events
   */
  def zeroSelector[T]: Selector[T] = (x: T) => new Channel.Zero[T]

  /** Picks channels in a Round Robin manner.
   *
   *  @tparam T       type of the events to route
   *  @param targets  the channels to route the events to
   *  @return         a selector function that chooses a channel
   */
  def roundRobin[T](targets: Seq[Channel[T]]): Selector[T] = {
    var i = -1
    (x: T) => {
      if (targets.nonEmpty) {
        i = (i + 1) % targets.length
        val ch = targets(i)
        ch
      } else new Channel.Zero[T]
    }
  }

  /** Picks a channel from a random distribution.
   *
   *  @tparam T        type of the events routed
   *  @param targets   target channels to which to route the events
   *  @param randfun   randomization function, total number of channels to an index
   *  @return          the selector function
   */
  def random[T](
    targets: Seq[Channel[T]],
    randfun: Int => Int = {
      val r = new Random
      (n: Int) => r.nextInt(n)
    }
  ): Selector[T] = {
    (x: T) => {
      if (targets.nonEmpty) targets(randfun(targets.length))
      else new Channel.Zero[T]
    }
  }

  /** Consistently picks a channel using a hashing function on the event.
   *
   *  The hashing function is applied to the event, and the hash code is used to
   *  select a channel. Given that the same hash code is always returned for the same
   *  event, the same channel is always picked. This is useful when implementing, e.g.
   *  distributed hash tables.
   *
   *  @tparam T        type of the events routed
   *  @param targets   target channels to which events are routed
   *  @param hashing   hashing function from an event to some hash value
   *  @return          the selector function
   */
  def hash[T](
    targets: Seq[Channel[T]],
    hashing: T => Int = (x: T) => x.##
  ): Selector[T] = {
    (x: T) => {
      if (targets.nonEmpty) targets(hashing(x) % targets.length)
      else new Channel.Zero[T]
    }
  }

  /** Picks the next channel according to the Deficit Round Robin routing algorithm.
   *
   *  This routing policy attempts to send the message to the channel that has so far
   *  received the least total cost, according to some cost function `cost`.
   *  The cost of an event could be its size (if the network transmission is the main
   *  concern), or the estimate on the processing time of that event (if computing
   *  bandwidth is the main concern).
   *
   *  Each target channel has an associated deficit counter, which is increased by
   *  an amount called a `quantum` each time a channel gets selected, and decreased
   *  every time that an event is sent to it. When an event with a cost higher than
   *  the deficit counter appears, the next channel is selected.
   *
   *  '''Note:''' quantum and cost should be relatively close in magnitude.
   *
   *  @tparam T       type of routed events
   *  @param targets  sequence of target channels
   *  @param quantum  the base cost quantum used to increase 
   *  @param cost     function from an event to its cost
   *  @return         a selector
   */
  def deficitRoundRobin[T](
    targets: immutable.Seq[Channel[T]],
    quantum: Int,
    cost: T => Int
  ): Selector[T] = {
    if (targets.isEmpty) zeroSelector
    else {
      val deficits = new Array[Int](targets.length)
      var i = targets.length - 1
      (x: T) => {
        val c = cost(x)
        var found = false
        while (!found) {
          if (deficits(i) > c) found = true
          else {
            i = (i + 1) % targets.length
            deficits(i) += quantum
          }
        }
        deficits(i) -= c
        targets(i)
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy