use std::{
collections::VecDeque,
fmt::{self, Debug},
sync::{Arc, Mutex},
thread,
time::{Duration, Instant},
};
use std_semaphore::Semaphore;
pub mod interval;
pub type IntervalFn = dyn Fn(Option<&ThrottleLog>) -> Duration + Send + Sync + 'static;
pub struct Throttle {
allowed_future: Mutex<Instant>,
semaphore: Semaphore,
log: Option<Mutex<ThrottleLog>>,
interval_fn: Arc<IntervalFn>,
concurrent: u32,
}
impl Throttle {
pub fn builder() -> ThrottleBuilder {
ThrottleBuilder::new()
}
pub fn run<F, T>(&self, f: F) -> T
where
F: FnOnce() -> T,
{
let _semaphore_guard = self.semaphore.access();
self.waiting();
let result = f();
self.write_log(true);
result
}
pub fn run_fallible<F, T, E>(&self, f: F) -> Result<T, E>
where
F: FnOnce() -> Result<T, E>,
{
let _semaphore_guard = self.semaphore.access();
self.waiting();
let result = f();
self.write_log(result.is_ok());
result
}
pub fn retry<F, T, E, R>(&self, mut f: F, max_retry: usize) -> Result<T, E>
where
F: FnMut(usize) -> R,
R: Into<RetryableResult<T, E>>,
{
let max_try = max_retry + 1;
let mut round = 1;
loop {
let _semaphore_guard = self.semaphore.access();
self.waiting();
let result: RetryableResult<T, E> = f(round).into();
match result {
RetryableResult::Ok(v) => {
self.write_log(true);
return Ok(v);
}
RetryableResult::RetryableErr(e) => {
self.write_log(false);
if round == max_try {
return Err(e);
} else {
round += 1;
}
}
RetryableResult::FatalErr(e) => {
self.write_log(false);
return Err(e);
}
};
}
}
fn waiting(&self) {
let still_should_wait: Option<Duration> = {
let mut allowed_future_guard = self
.allowed_future
.lock()
.expect("mutex impossible to be poison");
let next_interval: Duration = (self.interval_fn)(
self.log
.as_ref()
.map(|log| log.lock().expect("mutex impossible to be poison"))
.as_deref(),
) / self.concurrent;
let allowed_future = *allowed_future_guard;
let now = Instant::now();
let next_allowed_future_baseline = *[now, allowed_future]
.iter()
.max()
.expect("this is [Instant; 2] array so max value always exists");
let next_allowed_future = next_allowed_future_baseline + next_interval;
*allowed_future_guard = next_allowed_future;
drop(allowed_future_guard);
allowed_future.checked_duration_since(now)
};
if let Some(still_should_wait) = still_should_wait {
thread::sleep(still_should_wait);
}
}
fn write_log(&self, successful: bool) {
if let Some(log) = self.log.as_ref() {
log.lock()
.expect("mutex impossible to be poison")
.push(LogRecord {
time: Instant::now(),
successful,
});
}
}
}
impl Debug for Throttle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Throttle")
.field("allowed_future", &self.allowed_future)
.field("concurrent", &self.concurrent)
.finish()
}
}
pub struct ThrottleBuilder {
interval_fn: Arc<IntervalFn>,
concurrent: u32,
log_size: usize,
}
impl ThrottleBuilder {
fn new() -> Self {
Self {
interval_fn: Arc::new(|_| Duration::default()),
concurrent: 1,
log_size: 0,
}
}
pub fn interval<I>(&mut self, interval: I) -> &mut Self
where
I: Into<Interval>,
{
let interval = interval.into();
self.interval_fn = interval.interval_fn;
self.log_size = interval.log_size;
self
}
pub fn concurrent(&mut self, concurrent: u32) -> &mut Self {
self.concurrent = concurrent;
self
}
pub fn build(&self) -> Option<Throttle> {
use std::convert::TryInto;
if self.concurrent == 0 {
return None;
}
Some(Throttle {
allowed_future: Mutex::new(Instant::now()),
log: match self.log_size {
0 => None,
_ => Some(Mutex::new(ThrottleLog::new(self.log_size))),
},
semaphore: Semaphore::new(self.concurrent.try_into().ok()?),
interval_fn: Arc::clone(&self.interval_fn),
concurrent: self.concurrent,
})
}
}
impl Debug for ThrottleBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ThrottleBuilder")
.field("concurrent", &self.concurrent)
.field("log_size", &self.log_size)
.finish()
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum RetryableResult<T, E> {
Ok(T),
RetryableErr(E),
FatalErr(E),
}
impl<T, E> From<Result<T, E>> for RetryableResult<T, E> {
fn from(result: Result<T, E>) -> Self {
match result {
Ok(v) => Self::Ok(v),
Err(e) => Self::RetryableErr(e),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ThrottleLog {
size: usize,
inner: VecDeque<LogRecord>,
}
impl ThrottleLog {
fn new(size: usize) -> Self {
Self {
size,
inner: VecDeque::with_capacity(size),
}
}
fn push(&mut self, log_record: LogRecord) {
if self.size == 0 {
return;
}
if self.size == self.inner.len() {
self.inner.pop_back();
}
self.inner.push_front(log_record);
}
pub fn size(&self) -> usize {
self.size
}
pub fn failure_count(&self) -> usize {
self.inner
.iter()
.filter(|record| !record.successful)
.count()
}
pub fn failure_count_cont(&self) -> usize {
self.inner
.iter()
.take_while(|record| !record.successful)
.count()
}
pub fn failure_rate(&self) -> Option<f64> {
if self.size == 0 {
None
} else {
let failed_count = self.failure_count();
Some(failed_count as f64 / self.size as f64)
}
}
pub fn duration(&self) -> Option<Duration> {
if self.inner.len() <= 1 {
None
} else {
Some(self.inner.front().unwrap().time - self.inner.back().unwrap().time)
}
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct LogRecord {
time: Instant,
successful: bool,
}
#[derive(Clone)]
pub struct Interval {
interval_fn: Arc<IntervalFn>,
log_size: usize,
}
impl Interval {
pub fn new<F>(interval_fn: F, log_size: usize) -> Self
where
F: Fn(Option<&ThrottleLog>) -> Duration + Send + Sync + 'static,
{
Self {
interval_fn: Arc::new(interval_fn),
log_size,
}
}
pub fn modify<F>(self, f: F) -> Interval
where
F: Fn(Duration) -> Duration + Send + Sync + 'static,
{
let orig_fn = self.interval_fn;
Self {
interval_fn: Arc::new(move |log| f(orig_fn(log))),
log_size: self.log_size,
}
}
}
impl<F> From<F> for Interval
where
F: Fn() -> Duration + Send + Sync + 'static,
{
fn from(f: F) -> Self {
Self {
interval_fn: Arc::new(move |_| f()),
log_size: 0,
}
}
}
impl From<Duration> for Interval {
fn from(duration: Duration) -> Self {
Self {
interval_fn: Arc::new(move |_| duration),
log_size: 0,
}
}
}
impl Debug for Interval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Interval")
.field("log_size", &self.log_size)
.finish()
}
}
impl Default for Interval {
fn default() -> Self {
Self {
interval_fn: Arc::new(|_| Duration::default()),
log_size: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn with_concurrent_equal_0() {
assert!(Throttle::builder().concurrent(0).build().is_none());
}
#[test]
fn with_concurrent_equal_to_isize_max() {
assert!(Throttle::builder()
.concurrent(isize::MAX as u32)
.build()
.is_some());
}
#[test]
#[cfg(any(
target_pointer_width = "8",
target_pointer_width = "16",
target_pointer_width = "32",
))]
fn with_concurrent_large_than_isize_max() {
assert!(Throttle::builder()
.concurrent(isize::MAX as u32 + 1)
.build()
.is_none());
}
#[test]
fn retryable_result_convert() {
let orig: Result<bool, u32> = Err(42);
let to: RetryableResult<bool, u32> = orig.into();
assert_eq!(to, RetryableResult::RetryableErr(42))
}
#[test]
fn throttle_log_op() {
let mut log = ThrottleLog::new(4);
assert_eq!(log.failure_count_cont(), 0);
assert_eq!(log.failure_count(), 0);
assert_eq!(log.failure_rate().unwrap(), 0.0);
log.push(LogRecord {
time: Instant::now(),
successful: false,
});
assert_eq!(log.failure_count_cont(), 1);
assert_eq!(log.failure_count(), 1);
assert_eq!(log.failure_rate().unwrap(), 0.25);
log.push(LogRecord {
time: Instant::now(),
successful: false,
});
assert_eq!(log.failure_count_cont(), 2);
assert_eq!(log.failure_count(), 2);
assert_eq!(log.failure_rate().unwrap(), 0.5);
log.push(LogRecord {
time: Instant::now(),
successful: true,
});
log.push(LogRecord {
time: Instant::now(),
successful: true,
});
assert_eq!(log.failure_count_cont(), 0);
assert_eq!(log.failure_count(), 2);
assert_eq!(log.failure_rate().unwrap(), 0.5);
log.push(LogRecord {
time: Instant::now(),
successful: true,
});
assert_eq!(log.failure_count_cont(), 0);
assert_eq!(log.failure_count(), 1);
assert_eq!(log.failure_rate().unwrap(), 0.25);
log.push(LogRecord {
time: Instant::now(),
successful: false,
});
assert_eq!(log.failure_count_cont(), 1);
assert_eq!(log.failure_count(), 1);
assert_eq!(log.failure_rate().unwrap(), 0.25);
}
#[test]
fn throttle_log_new_0() {
let mut log = ThrottleLog::new(0);
log.push(LogRecord {
time: Instant::now(),
successful: false,
});
assert_eq!(log.failure_count_cont(), 0);
assert_eq!(log.failure_count(), 0);
assert!(log.failure_rate().is_none());
}
#[test]
fn interval_modify() {
let algo = Interval::new(|_| Duration::from_millis(10), 0).modify(|dur| dur * 2);
assert_eq!((algo.interval_fn)(None), Duration::from_millis(20));
}
}