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

com.twitter.finagle.group.StabilizingGroup.scala Maven / Gradle / Ivy

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

import com.twitter.concurrent.{Offer, Broker}
import com.twitter.finagle.builder.Cluster
import com.twitter.finagle.Group
import com.twitter.finagle.stats.{StatsReceiver, NullStatsReceiver}
import com.twitter.finagle.util.DefaultTimer
import com.twitter.util.{Future, Time, Timer, Duration, Var}
import scala.collection.immutable.Queue

@deprecated("Use StabilizingAddr instead", "6.7.5")
object StabilizingGroup {
  object State extends Enumeration {
    type Health = Value
    // explicitly define intuitive ids as they
    // are exported for stats.
    val Healthy = Value(1)
    val Unknown = Value(0)
    val Unhealthy = Value(-1)
  }

  @deprecated("Use StabilizingAddr instead", "6.7.5")
  def apply[T](
    underlying: Group[T],
    pulse: Offer[State.Health],
    grace: Duration,
    statsReceiver: StatsReceiver = NullStatsReceiver,
    timer: Timer = DefaultTimer.twitter
  ): Group[T] = {
    new StabilizingGroup(underlying, pulse, grace, statsReceiver, timer)
  }

  /**
   * A StabilizingGroup conservatively removes elements
   * from a Group depending on the `pulse`. More specifically,
   * removes are delayed until the group is in a healthy state
   * for at least `grace` period.
   *
   * @param underlying The source group to poll for group updates.
   * @param pulse An offer for communicating group health.
   *        Invariant: The offer should communicate Health in FIFO order
   *        with respect to time.
   * @param grace The duration that must elapse before an element
   *        is removed from the group.
   */
  private[this] class StabilizingGroup[T](
    underlying: Group[T],
    pulse: Offer[State.Health],
    grace: Duration,
    statsReceiver: StatsReceiver,
    implicit val timer: Timer
  ) extends Group[T] {
    import State._

    private[this] val newSet = new Broker[Set[T]]()

    @volatile private[this] var healthStat = Healthy.id
    private[this] val health = statsReceiver.addGauge("health") { healthStat }

    private[this] val limbo = statsReceiver.addGauge("limbo") {
      members.size - underlying.members.size
    }

    protected[finagle] val set = Var(underlying.members)

    /**
     * Exclusively maintains the elements in current
     * based on adds, removes, and health transitions.
     * Removes are delayed for grace period and each health
     * transition resets the grace period.
     */
    private[this] def loop(remq: Queue[(T, Time)], h: Health): Future[Unit] = Offer.select(
      pulse map { newh =>
        healthStat = newh.id

        // if our health transitions into healthy,
        // reset removal times foreach elem in remq.
        newh match {
          case newh if h == newh =>
            loop(remq, newh)
          case Healthy =>
            // Transitioned to healthy: push back
            val newTime = Time.now + grace
            val newq = remq map { case (elem, _) => (elem, newTime) }
            loop(newq, Healthy)
          case newh =>
            loop(remq, newh)
        }
      },
      newSet.recv map { newSet =>
        val snap  = set()
        var q = remq

        // Remove pending removes that are present
        // in this update.
        q = q filter { case (e, _) => !(newSet contains e) }
        set() ++= newSet &~ snap

        def inQ(elem: T) = q exists { case (e, _) => e == elem }
        for (el <- snap &~ newSet if !inQ(el)) {
          val until = Time.now + grace
          q = q enqueue (el, until)
        }

        loop(q, h)
      },
      if (h != Healthy || remq.isEmpty) Offer.never
      else {
        val ((elem, until), nextq) = remq.dequeue
        Offer.timeout(until - Time.now) map { _ =>
          set() -= elem
          loop(nextq, h)
        }
      }
    )

    loop(Queue.empty, Healthy)

    underlying.set observe { set =>
      // We can synchronize here because we know loop
      // is eager, and doesn't itself synchronize.
      newSet !! set
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy