add mock server

This commit is contained in:
appflowy
2021-12-13 13:55:44 +08:00
parent df432d11c3
commit 31086ad4df
51 changed files with 423 additions and 292 deletions

View File

@ -1,19 +0,0 @@
[package]
name = "lib-infra"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
uuid = { version = "0.8", features = ["serde", "v4"] }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
protobuf = {version = "2.18.0"}
log = "0.4.14"
chrono = "0.4.19"
bytes = { version = "1.0" }
pin-project = "1.0"
futures-core = { version = "0.3", default-features = false }
tokio = { version = "1.0", features = ["time", "rt"] }
rand = "0.8.3"

View File

@ -1,64 +0,0 @@
use futures_core::ready;
use pin_project::pin_project;
use std::{
fmt::Debug,
future::Future,
pin::Pin,
task::{Context, Poll},
};
pub fn wrap_future<T, O>(f: T) -> FnFuture<O>
where
T: Future<Output = O> + Send + Sync + 'static,
{
FnFuture { fut: Box::pin(f) }
}
#[pin_project]
pub struct FnFuture<T> {
#[pin]
pub fut: Pin<Box<dyn Future<Output = T> + Sync + Send>>,
}
impl<T> Future for FnFuture<T>
where
T: Send + Sync,
{
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.as_mut().project();
Poll::Ready(ready!(this.fut.poll(cx)))
}
}
#[pin_project]
pub struct ResultFuture<T, E> {
#[pin]
pub fut: Pin<Box<dyn Future<Output = Result<T, E>> + Sync + Send>>,
}
impl<T, E> ResultFuture<T, E> {
pub fn new<F>(f: F) -> Self
where
F: Future<Output = Result<T, E>> + Send + Sync + 'static,
{
Self {
fut: Box::pin(async { f.await }),
}
}
}
impl<T, E> Future for ResultFuture<T, E>
where
T: Send + Sync,
E: Debug,
{
type Output = Result<T, E>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.as_mut().project();
let result = ready!(this.fut.poll(cx));
Poll::Ready(result)
}
}

View File

@ -1,8 +0,0 @@
pub mod future;
pub mod retry;
#[allow(dead_code)]
pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }
#[allow(dead_code)]
pub fn timestamp() -> i64 { chrono::Utc::now().timestamp() }

View File

@ -1,209 +0,0 @@
#![allow(clippy::large_enum_variant)]
#![allow(clippy::type_complexity)]
use crate::retry::FixedInterval;
use pin_project::pin_project;
use std::{
future::Future,
iter::{IntoIterator, Iterator},
pin::Pin,
task::{Context, Poll},
};
use tokio::{
task::JoinHandle,
time::{sleep_until, Duration, Instant, Sleep},
};
#[pin_project(project = RetryStateProj)]
enum RetryState<A>
where
A: Action,
{
Running(#[pin] A::Future),
Sleeping(#[pin] Sleep),
}
impl<A: Action> RetryState<A> {
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> RetryFuturePoll<A> {
match self.project() {
RetryStateProj::Running(future) => RetryFuturePoll::Running(future.poll(cx)),
RetryStateProj::Sleeping(future) => RetryFuturePoll::Sleeping(future.poll(cx)),
}
}
}
enum RetryFuturePoll<A>
where
A: Action,
{
Running(Poll<Result<A::Item, A::Error>>),
Sleeping(Poll<()>),
}
/// Future that drives multiple attempts at an action via a retry strategy.
#[pin_project]
pub struct Retry<I, A>
where
I: Iterator<Item = Duration>,
A: Action,
{
#[pin]
retry_if: RetryIf<I, A, fn(&A::Error) -> bool>,
}
impl<I, A> Retry<I, A>
where
I: Iterator<Item = Duration>,
A: Action,
{
pub fn spawn<T: IntoIterator<IntoIter = I, Item = Duration>>(strategy: T, action: A) -> Retry<I, A> {
Retry {
retry_if: RetryIf::spawn(strategy, action, (|_| true) as fn(&A::Error) -> bool),
}
}
}
impl<I, A> Future for Retry<I, A>
where
I: Iterator<Item = Duration>,
A: Action,
{
type Output = Result<A::Item, A::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
this.retry_if.poll(cx)
}
}
/// Future that drives multiple attempts at an action via a retry strategy.
/// Retries are only attempted if the `Error` returned by the future satisfies a
/// given condition.
#[pin_project]
pub struct RetryIf<I, A, C>
where
I: Iterator<Item = Duration>,
A: Action,
C: Condition<A::Error>,
{
strategy: I,
#[pin]
state: RetryState<A>,
action: A,
condition: C,
}
impl<I, A, C> RetryIf<I, A, C>
where
I: Iterator<Item = Duration>,
A: Action,
C: Condition<A::Error>,
{
pub fn spawn<T: IntoIterator<IntoIter = I, Item = Duration>>(
strategy: T,
mut action: A,
condition: C,
) -> RetryIf<I, A, C> {
RetryIf {
strategy: strategy.into_iter(),
state: RetryState::Running(action.run()),
action,
condition,
}
}
fn attempt(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<A::Item, A::Error>> {
let future = {
let this = self.as_mut().project();
this.action.run()
};
self.as_mut().project().state.set(RetryState::Running(future));
self.poll(cx)
}
fn retry(
mut self: Pin<&mut Self>,
err: A::Error,
cx: &mut Context,
) -> Result<Poll<Result<A::Item, A::Error>>, A::Error> {
match self.as_mut().project().strategy.next() {
None => Err(err),
Some(duration) => {
let deadline = Instant::now() + duration;
let future = sleep_until(deadline);
self.as_mut().project().state.set(RetryState::Sleeping(future));
Ok(self.poll(cx))
},
}
}
}
impl<I, A, C> Future for RetryIf<I, A, C>
where
I: Iterator<Item = Duration>,
A: Action,
C: Condition<A::Error>,
{
type Output = Result<A::Item, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match self.as_mut().project().state.poll(cx) {
RetryFuturePoll::Running(poll_result) => match poll_result {
Poll::Ready(Ok(ok)) => Poll::Ready(Ok(ok)),
Poll::Pending => Poll::Pending,
Poll::Ready(Err(err)) => {
if self.as_mut().project().condition.should_retry(&err) {
match self.retry(err, cx) {
Ok(poll) => poll,
Err(err) => Poll::Ready(Err(err)),
}
} else {
Poll::Ready(Err(err))
}
},
},
RetryFuturePoll::Sleeping(poll_result) => match poll_result {
Poll::Pending => Poll::Pending,
Poll::Ready(_) => self.attempt(cx),
},
}
}
}
/// An action can be run multiple times and produces a future.
pub trait Action: Send + Sync {
type Future: Future<Output = Result<Self::Item, Self::Error>>;
type Item;
type Error;
fn run(&mut self) -> Self::Future;
}
// impl<R, E, T: Future<Output = Result<R, E>>, F: FnMut() -> T + Send + Sync>
// Action for F { type Future = T;
// type Item = R;
// type Error = E;
//
// fn run(&mut self) -> Self::Future { self() }
// }
pub trait Condition<E> {
fn should_retry(&mut self, error: &E) -> bool;
}
impl<E, F: FnMut(&E) -> bool> Condition<E> for F {
fn should_retry(&mut self, error: &E) -> bool { self(error) }
}
pub fn spawn_retry<A: Action + 'static>(
millis: u64,
retry_count: usize,
action: A,
) -> JoinHandle<Result<A::Item, A::Error>>
where
A::Item: Send + Sync,
A::Error: Send + Sync,
<A as Action>::Future: Send + Sync,
{
let strategy = FixedInterval::from_millis(millis).take(retry_count);
let retry = Retry::spawn(strategy, action);
tokio::spawn(async move { retry.await })
}

View File

@ -1,5 +0,0 @@
mod future;
mod strategy;
pub use future::*;
pub use strategy::*;

View File

@ -1,127 +0,0 @@
use std::{iter::Iterator, time::Duration};
/// A retry strategy driven by exponential back-off.
///
/// The power corresponds to the number of past attempts.
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
current: u64,
base: u64,
factor: u64,
max_delay: Option<Duration>,
}
impl ExponentialBackoff {
/// Constructs a new exponential back-off strategy,
/// given a base duration in milliseconds.
///
/// The resulting duration is calculated by taking the base to the `n`-th
/// power, where `n` denotes the number of past attempts.
pub fn from_millis(base: u64) -> ExponentialBackoff {
ExponentialBackoff {
current: base,
base,
factor: 1u64,
max_delay: None,
}
}
/// A multiplicative factor that will be applied to the retry delay.
///
/// For example, using a factor of `1000` will make each delay in units of
/// seconds.
///
/// Default factor is `1`.
pub fn factor(mut self, factor: u64) -> ExponentialBackoff {
self.factor = factor;
self
}
/// Apply a maximum delay. No retry delay will be longer than this
/// `Duration`.
pub fn max_delay(mut self, duration: Duration) -> ExponentialBackoff {
self.max_delay = Some(duration);
self
}
}
impl Iterator for ExponentialBackoff {
type Item = Duration;
fn next(&mut self) -> Option<Duration> {
// set delay duration by applying factor
let duration = if let Some(duration) = self.current.checked_mul(self.factor) {
Duration::from_millis(duration)
} else {
Duration::from_millis(u64::MAX)
};
// check if we reached max delay
if let Some(ref max_delay) = self.max_delay {
if duration > *max_delay {
return Some(*max_delay);
}
}
if let Some(next) = self.current.checked_mul(self.base) {
self.current = next;
} else {
self.current = u64::MAX;
}
Some(duration)
}
}
#[test]
fn returns_some_exponential_base_10() {
let mut s = ExponentialBackoff::from_millis(10);
assert_eq!(s.next(), Some(Duration::from_millis(10)));
assert_eq!(s.next(), Some(Duration::from_millis(100)));
assert_eq!(s.next(), Some(Duration::from_millis(1000)));
}
#[test]
fn returns_some_exponential_base_2() {
let mut s = ExponentialBackoff::from_millis(2);
assert_eq!(s.next(), Some(Duration::from_millis(2)));
assert_eq!(s.next(), Some(Duration::from_millis(4)));
assert_eq!(s.next(), Some(Duration::from_millis(8)));
}
#[test]
fn saturates_at_maximum_value() {
let mut s = ExponentialBackoff::from_millis(u64::MAX - 1);
assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX - 1)));
assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX)));
assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX)));
}
#[test]
fn can_use_factor_to_get_seconds() {
let factor = 1000;
let mut s = ExponentialBackoff::from_millis(2).factor(factor);
assert_eq!(s.next(), Some(Duration::from_secs(2)));
assert_eq!(s.next(), Some(Duration::from_secs(4)));
assert_eq!(s.next(), Some(Duration::from_secs(8)));
}
#[test]
fn stops_increasing_at_max_delay() {
let mut s = ExponentialBackoff::from_millis(2).max_delay(Duration::from_millis(4));
assert_eq!(s.next(), Some(Duration::from_millis(2)));
assert_eq!(s.next(), Some(Duration::from_millis(4)));
assert_eq!(s.next(), Some(Duration::from_millis(4)));
}
#[test]
fn returns_max_when_max_less_than_base() {
let mut s = ExponentialBackoff::from_millis(20).max_delay(Duration::from_millis(10));
assert_eq!(s.next(), Some(Duration::from_millis(10)));
assert_eq!(s.next(), Some(Duration::from_millis(10)));
}

View File

@ -1,35 +0,0 @@
use std::{iter::Iterator, time::Duration};
/// A retry strategy driven by a fixed interval.
#[derive(Debug, Clone)]
pub struct FixedInterval {
duration: Duration,
}
impl FixedInterval {
/// Constructs a new fixed interval strategy.
pub fn new(duration: Duration) -> FixedInterval { FixedInterval { duration } }
/// Constructs a new fixed interval strategy,
/// given a duration in milliseconds.
pub fn from_millis(millis: u64) -> FixedInterval {
FixedInterval {
duration: Duration::from_millis(millis),
}
}
}
impl Iterator for FixedInterval {
type Item = Duration;
fn next(&mut self) -> Option<Duration> { Some(self.duration) }
}
#[test]
fn returns_some_fixed() {
let mut s = FixedInterval::new(Duration::from_millis(123));
assert_eq!(s.next(), Some(Duration::from_millis(123)));
assert_eq!(s.next(), Some(Duration::from_millis(123)));
assert_eq!(s.next(), Some(Duration::from_millis(123)));
}

View File

@ -1,3 +0,0 @@
use std::time::Duration;
pub fn jitter(duration: Duration) -> Duration { duration.mul_f64(rand::random::<f64>()) }

View File

@ -1,7 +0,0 @@
mod exponential_backoff;
mod fixed_interval;
mod jitter;
pub use exponential_backoff::*;
pub use fixed_interval::*;
pub use jitter::*;