blob: f85d2419c05adc0c0fb8e3093d71df2c26831bbf [file] [log] [blame]
use super::super::error;
use crate::discover::{Change, Discover};
use crate::load::Load;
use crate::ready_cache::{error::Failed, ReadyCache};
use futures_core::ready;
use futures_util::future::{self, TryFutureExt};
use pin_project_lite::pin_project;
use rand::{rngs::SmallRng, Rng, SeedableRng};
use std::hash::Hash;
use std::marker::PhantomData;
use std::{
fmt,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tokio::sync::oneshot;
use tower_service::Service;
use tracing::{debug, trace};
/// Efficiently distributes requests across an arbitrary number of services.
///
/// See the [module-level documentation](..) for details.
///
/// Note that [`Balance`] requires that the [`Discover`] you use is [`Unpin`] in order to implement
/// [`Service`]. This is because it needs to be accessed from [`Service::poll_ready`], which takes
/// `&mut self`. You can achieve this easily by wrapping your [`Discover`] in [`Box::pin`] before you
/// construct the [`Balance`] instance. For more details, see [#319].
///
/// [`Box::pin`]: std::boxed::Box::pin()
/// [#319]: https://github.com/tower-rs/tower/issues/319
pub struct Balance<D, Req>
where
D: Discover,
D::Key: Hash,
{
discover: D,
services: ReadyCache<D::Key, D::Service, Req>,
ready_index: Option<usize>,
rng: SmallRng,
_req: PhantomData<Req>,
}
impl<D: Discover, Req> fmt::Debug for Balance<D, Req>
where
D: fmt::Debug,
D::Key: Hash + fmt::Debug,
D::Service: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Balance")
.field("discover", &self.discover)
.field("services", &self.services)
.finish()
}
}
pin_project! {
/// A Future that becomes satisfied when an `S`-typed service is ready.
///
/// May fail due to cancelation, i.e., if [`Discover`] removes the service from the service set.
struct UnreadyService<K, S, Req> {
key: Option<K>,
#[pin]
cancel: oneshot::Receiver<()>,
service: Option<S>,
_req: PhantomData<Req>,
}
}
enum Error<E> {
Inner(E),
Canceled,
}
impl<D, Req> Balance<D, Req>
where
D: Discover,
D::Key: Hash,
D::Service: Service<Req>,
<D::Service as Service<Req>>::Error: Into<crate::BoxError>,
{
/// Constructs a load balancer that uses operating system entropy.
pub fn new(discover: D) -> Self {
Self::from_rng(discover, &mut rand::thread_rng()).expect("ThreadRNG must be valid")
}
/// Constructs a load balancer seeded with the provided random number generator.
pub fn from_rng<R: Rng>(discover: D, rng: R) -> Result<Self, rand::Error> {
let rng = SmallRng::from_rng(rng)?;
Ok(Self {
rng,
discover,
services: ReadyCache::default(),
ready_index: None,
_req: PhantomData,
})
}
/// Returns the number of endpoints currently tracked by the balancer.
pub fn len(&self) -> usize {
self.services.len()
}
/// Returns whether or not the balancer is empty.
pub fn is_empty(&self) -> bool {
self.services.is_empty()
}
}
impl<D, Req> Balance<D, Req>
where
D: Discover + Unpin,
D::Key: Hash + Clone,
D::Error: Into<crate::BoxError>,
D::Service: Service<Req> + Load,
<D::Service as Load>::Metric: std::fmt::Debug,
<D::Service as Service<Req>>::Error: Into<crate::BoxError>,
{
/// Polls `discover` for updates, adding new items to `not_ready`.
///
/// Removals may alter the order of either `ready` or `not_ready`.
fn update_pending_from_discover(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<(), error::Discover>>> {
debug!("updating from discover");
loop {
match ready!(Pin::new(&mut self.discover).poll_discover(cx))
.transpose()
.map_err(|e| error::Discover(e.into()))?
{
None => return Poll::Ready(None),
Some(Change::Remove(key)) => {
trace!("remove");
self.services.evict(&key);
}
Some(Change::Insert(key, svc)) => {
trace!("insert");
// If this service already existed in the set, it will be
// replaced as the new one becomes ready.
self.services.push(key, svc);
}
}
}
}
fn promote_pending_to_ready(&mut self, cx: &mut Context<'_>) {
loop {
match self.services.poll_pending(cx) {
Poll::Ready(Ok(())) => {
// There are no remaining pending services.
debug_assert_eq!(self.services.pending_len(), 0);
break;
}
Poll::Pending => {
// None of the pending services are ready.
debug_assert!(self.services.pending_len() > 0);
break;
}
Poll::Ready(Err(error)) => {
// An individual service was lost; continue processing
// pending services.
debug!(%error, "dropping failed endpoint");
}
}
}
trace!(
ready = %self.services.ready_len(),
pending = %self.services.pending_len(),
"poll_unready"
);
}
/// Performs P2C on inner services to find a suitable endpoint.
fn p2c_ready_index(&mut self) -> Option<usize> {
match self.services.ready_len() {
0 => None,
1 => Some(0),
len => {
// Get two distinct random indexes (in a random order) and
// compare the loads of the service at each index.
let idxs = rand::seq::index::sample(&mut self.rng, len, 2);
let aidx = idxs.index(0);
let bidx = idxs.index(1);
debug_assert_ne!(aidx, bidx, "random indices must be distinct");
let aload = self.ready_index_load(aidx);
let bload = self.ready_index_load(bidx);
let chosen = if aload <= bload { aidx } else { bidx };
trace!(
a.index = aidx,
a.load = ?aload,
b.index = bidx,
b.load = ?bload,
chosen = if chosen == aidx { "a" } else { "b" },
"p2c",
);
Some(chosen)
}
}
}
/// Accesses a ready endpoint by index and returns its current load.
fn ready_index_load(&self, index: usize) -> <D::Service as Load>::Metric {
let (_, svc) = self.services.get_ready_index(index).expect("invalid index");
svc.load()
}
pub(crate) fn discover_mut(&mut self) -> &mut D {
&mut self.discover
}
}
impl<D, Req> Service<Req> for Balance<D, Req>
where
D: Discover + Unpin,
D::Key: Hash + Clone,
D::Error: Into<crate::BoxError>,
D::Service: Service<Req> + Load,
<D::Service as Load>::Metric: std::fmt::Debug,
<D::Service as Service<Req>>::Error: Into<crate::BoxError>,
{
type Response = <D::Service as Service<Req>>::Response;
type Error = crate::BoxError;
type Future = future::MapErr<
<D::Service as Service<Req>>::Future,
fn(<D::Service as Service<Req>>::Error) -> crate::BoxError,
>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// `ready_index` may have already been set by a prior invocation. These
// updates cannot disturb the order of existing ready services.
let _ = self.update_pending_from_discover(cx)?;
self.promote_pending_to_ready(cx);
loop {
// If a service has already been selected, ensure that it is ready.
// This ensures that the underlying service is ready immediately
// before a request is dispatched to it (i.e. in the same task
// invocation). If, e.g., a failure detector has changed the state
// of the service, it may be evicted from the ready set so that
// another service can be selected.
if let Some(index) = self.ready_index.take() {
match self.services.check_ready_index(cx, index) {
Ok(true) => {
// The service remains ready.
self.ready_index = Some(index);
return Poll::Ready(Ok(()));
}
Ok(false) => {
// The service is no longer ready. Try to find a new one.
trace!("ready service became unavailable");
}
Err(Failed(_, error)) => {
// The ready endpoint failed, so log the error and try
// to find a new one.
debug!(%error, "endpoint failed");
}
}
}
// Select a new service by comparing two at random and using the
// lesser-loaded service.
self.ready_index = self.p2c_ready_index();
if self.ready_index.is_none() {
debug_assert_eq!(self.services.ready_len(), 0);
// We have previously registered interest in updates from
// discover and pending services.
return Poll::Pending;
}
}
}
fn call(&mut self, request: Req) -> Self::Future {
let index = self.ready_index.take().expect("called before ready");
self.services
.call_ready_index(index, request)
.map_err(Into::into)
}
}
impl<K, S: Service<Req>, Req> Future for UnreadyService<K, S, Req> {
type Output = Result<(K, S), (K, Error<S::Error>)>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Poll::Ready(Ok(())) = this.cancel.poll(cx) {
let key = this.key.take().expect("polled after ready");
return Poll::Ready(Err((key, Error::Canceled)));
}
let res = ready!(this
.service
.as_mut()
.expect("poll after ready")
.poll_ready(cx));
let key = this.key.take().expect("polled after ready");
let svc = this.service.take().expect("polled after ready");
match res {
Ok(()) => Poll::Ready(Ok((key, svc))),
Err(e) => Poll::Ready(Err((key, Error::Inner(e)))),
}
}
}
impl<K, S, Req> fmt::Debug for UnreadyService<K, S, Req>
where
K: fmt::Debug,
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self {
key,
cancel,
service,
_req,
} = self;
f.debug_struct("UnreadyService")
.field("key", key)
.field("cancel", cancel)
.field("service", service)
.finish()
}
}