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

com.twitter.finagle.serverset2.Stabilizer.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.serverset2

import com.twitter.conversions.time._
import com.twitter.finagle.{Addr, WeightedSocketAddress}
import com.twitter.util.{Event, Witness, Var, Timer, Duration}
import java.net.SocketAddress
import java.util.concurrent.atomic.AtomicInteger

private[serverset2] object Stabilizer {
  // Create an event of epochs for the given duration.
  def epochs(period: Duration)(implicit timer: Timer): Event[Unit] = 
    new Event[Unit] {
      def register(w: Witness[Unit]) = {
        timer.schedule(period) {
          w.notify(())
        }
      }
    }

  private case class State(
    limbo: Set[SocketAddress],
    active: Set[SocketAddress],
    addr: Addr)

  private val emptyState = State(Set.empty, Set.empty, Addr.Pending)

  /**
   * Stabilize the address relative to the supplied source of epochs,
   * such that any removed socket address in an Addr.Bound set is
   * placed in a limbo state until at least one epoch has passed.
   *
   * In practice, the source of epochs must correlate with a failure
   * detection interval; we consider an address dead if it has not
   * been observed for at least one epoch, and no failures
   * (Addr.Failed) have been observed in the same interval.
   */
  def apply(va: Var[Addr], epoch: Event[Unit])
  : Var[Addr] = Var.async(Addr.Pending: Addr) { u =>
    // We construct an Event[State] representing states after
    // successive address observations. The state contains two sets
    // of addresses: the "active" set is the set of all observed
    // addresses in the current epoch; the "limbo" set is active set
    // at the turn of the last epoch.
    //
    // Whenever a failure is observed, the limbo set promoted by
    // adding it to the active set; thus we can guarantee that set
    // limbo++active contains all addresses seen in at least one
    // epoch's period without intermittent failure.
    //
    // Our state also maintains the last observed value of `va`.
    //
    // Thus we interpret the stabilized address to be
    // Addr.Bound(limbo++active) when these are nonempty; otherwise
    // the last observed address.

    val states: Event[State] = (va.changes select epoch).foldLeft(emptyState) {
      // Addr update
      case (st@State(limbo, active, last), Left(addr)) =>
        addr match {
          case Addr.Failed(_) =>
            State(Set.empty, active++limbo, addr)

          case Addr.Bound(bound) =>
            State(limbo, merge(active, bound), addr)

          case addr => 
            // Any other address simply propagates the address while
            // leaving the active/limbo set unchanged. Both active
            // and limbo have to expire in order for this address to
            // propagate.
            st.copy(addr=addr)
        }
      
      // The epoch turned
      case (st@State(limbo, active, last), Right(())) =>
        last match {
          case Addr.Bound(bound) =>
            State(active, bound, last)

          case Addr.Neg =>
            State(active, Set.empty, Addr.Neg)

          case Addr.Pending | Addr.Failed(_) =>
            // If the last address is nonbound, we ignore it and
            // maintain our state; we cannot demote the active set
            // when nonbound, since that would eventually promote
            // the address
            st
        }
    }

    val addrs = states map { case State(limbo, active, last) =>
      val all = merge(limbo, active)
      if (all.nonEmpty) Addr.Bound(all)
      else last
    } sliding(2) collect {
      // Quench duplicate updates
      case Seq(a) => a
      case Seq(fst, snd) if fst != snd => snd
    }

    addrs.register(Witness(u))
  }

  /**
   * Merge WeightedSocketAddresses with same underlying SocketAddress
   * preferring weights from `next` over `prev`.
   */
  private def merge(prev: Set[SocketAddress], next: Set[SocketAddress]): Set[SocketAddress] = {
    val nextStripped = next map {
      case WeightedSocketAddress(sa, w) => sa
    }

    val legacy = prev filter {
      case WeightedSocketAddress(sa, w) => !nextStripped.contains(sa)
    }

    legacy ++ next
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy