use std::{
future::Future,
pin::Pin,
sync::{
atomic::{AtomicU8, Ordering},
Arc, Mutex,
},
task::{Context, Poll},
};
use futures::{ready, task::AtomicWaker};
use pin_project::pin_project;
use tower::{hedge, BoxError, Service};
use zebra_network as zn;
use super::{is_not_found, SYNC_RESTART_DELAY};
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub(super) struct SuccessHedge<S>(hedge::Hedge<ArmService<S>, HedgePolicy>);
impl<S> SuccessHedge<S>
where
S: Service<zn::Request> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<BoxError>,
{
pub(super) fn new(service: S) -> Self {
Self(hedge::Hedge::new(
ArmService {
inner: service,
ready_error: None,
},
HedgePolicy,
20,
0.95,
2 * SYNC_RESTART_DELAY,
))
}
}
impl<S> Service<zn::Request> for SuccessHedge<S>
where
S: Service<zn::Request> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<BoxError>,
{
type Response = S::Response;
type Error = BoxError;
type Future = <hedge::Hedge<ArmService<S>, HedgePolicy> as Service<HedgeRequest>>::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, request: zn::Request) -> Self::Future {
self.0.call(HedgeRequest {
request,
arm: Arm {
state: Arc::new(ArmState {
flags: AtomicU8::new(1),
wakers: [AtomicWaker::new(), AtomicWaker::new()],
error: Mutex::new(None),
}),
index: 0,
},
})
}
}
#[derive(Debug)]
struct ArmState {
flags: AtomicU8,
wakers: [AtomicWaker; 2],
error: Mutex<Option<BoxError>>,
}
#[derive(Debug)]
struct Arm {
state: Arc<ArmState>,
index: usize,
}
impl Arm {
fn finish(&self) {
let bit = 1 << self.index;
if self.state.flags.fetch_and(!bit, Ordering::AcqRel) & bit != 0 {
self.state.wakers[1 - self.index].wake();
}
}
}
impl Drop for Arm {
fn drop(&mut self) {
self.finish();
}
}
#[derive(Debug)]
pub(super) struct HedgeRequest {
request: zn::Request,
arm: Arm,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct HedgePolicy;
impl hedge::Policy<HedgeRequest> for HedgePolicy {
fn clone_request(&self, request: &HedgeRequest) -> Option<HedgeRequest> {
let payload = request.request.clone();
request.arm.state.flags.fetch_or(2, Ordering::AcqRel);
Some(HedgeRequest {
request: payload,
arm: Arm {
state: Arc::clone(&request.arm.state),
index: 1,
},
})
}
fn can_retry(&self, _request: &HedgeRequest) -> bool {
true
}
}
#[derive(Debug)]
pub(super) struct ArmService<S> {
inner: S,
ready_error: Option<BoxError>,
}
impl<S: Clone> Clone for ArmService<S> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
ready_error: None,
}
}
}
impl<S> Service<HedgeRequest> for ArmService<S>
where
S: Service<zn::Request> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<BoxError>,
{
type Response = S::Response;
type Error = BoxError;
type Future = ArmFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.ready_error.is_none() {
if let Err(error) = ready!(self.inner.poll_ready(cx)) {
self.ready_error = Some(error.into());
self.inner = self.inner.clone();
}
}
Poll::Ready(Ok(()))
}
fn call(&mut self, request: HedgeRequest) -> Self::Future {
let error = self.ready_error.take();
ArmFuture {
inner: error.is_none().then(|| self.inner.call(request.request)),
arm: request.arm,
error,
}
}
}
#[pin_project]
#[derive(Debug)]
pub(super) struct ArmFuture<F> {
#[pin]
inner: Option<F>,
arm: Arm,
error: Option<BoxError>,
}
impl<F, T, E> Future for ArmFuture<F>
where
F: Future<Output = Result<T, E>>,
E: Into<BoxError>,
{
type Output = Result<T, BoxError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
if let Some(inner) = this.inner.as_mut().as_pin_mut() {
let result = ready!(inner.poll(cx));
this.inner.set(None);
match result {
Ok(response) => {
this.arm.finish();
return Poll::Ready(Ok(response));
}
Err(error) => *this.error = Some(error.into()),
}
}
if let Some(error) = this.error.take() {
let mut preferred = this
.arm
.state
.error
.lock()
.expect("thread panicked while holding the hedge error mutex");
if preferred.is_none() || is_not_found(error.as_ref()) {
*preferred = Some(error);
}
}
this.arm.finish();
this.arm.state.wakers[this.arm.index].register(cx.waker());
if this.arm.state.flags.load(Ordering::Acquire) == 0 {
Poll::Ready(Err(this
.arm
.state
.error
.lock()
.expect("thread panicked while holding the hedge error mutex")
.take()
.expect("all arms failed before the hedge returns an error")))
} else {
Poll::Pending
}
}
}