mirror of
https://github.com/tower-rs/tower.git
synced 2025-10-02 15:25:11 +00:00
InFlightLimit middleware (#49)
Provides a middleware that sets a maximum number of requests that can be in-flight for the service. A request is defined to be in-flight from the time `call` is invoked to the time the returned response future resolves. This maximum is enforced across all clones of the service instance.
This commit is contained in:
parent
942238237e
commit
41c54b208e
@ -23,6 +23,7 @@ members = [
|
|||||||
"tower-buffer",
|
"tower-buffer",
|
||||||
"tower-discover",
|
"tower-discover",
|
||||||
"tower-filter",
|
"tower-filter",
|
||||||
|
"tower-in-flight-limit",
|
||||||
"tower-mock",
|
"tower-mock",
|
||||||
"tower-rate-limit",
|
"tower-rate-limit",
|
||||||
"tower-ready-service",
|
"tower-ready-service",
|
||||||
|
13
tower-in-flight-limit/Cargo.toml
Normal file
13
tower-in-flight-limit/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "tower-in-flight-limit"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Carl Lerche <me@carllerche.com>"]
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.1"
|
||||||
|
tower = { version = "0.1", path = "../" }
|
||||||
|
tower-ready-service = { version = "0.1", path = "../tower-ready-service" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tower-mock = { version = "0.1", path = "../tower-mock" }
|
4
tower-in-flight-limit/README.md
Normal file
4
tower-in-flight-limit/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Tower In-Flight Limit
|
||||||
|
|
||||||
|
A Tower middleware that limits the maximum number of in-flight requests for a
|
||||||
|
service.
|
243
tower-in-flight-limit/src/lib.rs
Normal file
243
tower-in-flight-limit/src/lib.rs
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
//! Tower middleware that limits the maximum number of in-flight requests for a
|
||||||
|
//! service.
|
||||||
|
|
||||||
|
extern crate futures;
|
||||||
|
extern crate tower;
|
||||||
|
extern crate tower_ready_service;
|
||||||
|
|
||||||
|
use tower::Service;
|
||||||
|
use tower_ready_service::ReadyService;
|
||||||
|
|
||||||
|
use futures::{Future, Poll, Async};
|
||||||
|
use futures::task::AtomicTask;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InFlightLimit<T> {
|
||||||
|
inner: T,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error returned when the service has reached its limit.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<T> {
|
||||||
|
NoCapacity,
|
||||||
|
Upstream(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResponseFuture<T> {
|
||||||
|
inner: Option<T>,
|
||||||
|
shared: Arc<Shared>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct State {
|
||||||
|
shared: Arc<Shared>,
|
||||||
|
reserved: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Shared {
|
||||||
|
max: usize,
|
||||||
|
curr: AtomicUsize,
|
||||||
|
task: AtomicTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl InFlightLimit =====
|
||||||
|
|
||||||
|
impl<T> InFlightLimit<T> {
|
||||||
|
/// Create a new rate limiter
|
||||||
|
pub fn new(inner: T, max: usize) -> Self {
|
||||||
|
InFlightLimit {
|
||||||
|
inner,
|
||||||
|
state: State {
|
||||||
|
shared: Arc::new(Shared {
|
||||||
|
max,
|
||||||
|
curr: AtomicUsize::new(0),
|
||||||
|
task: AtomicTask::new(),
|
||||||
|
}),
|
||||||
|
reserved: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the inner service
|
||||||
|
pub fn get_ref(&self) -> &T {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the inner service
|
||||||
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume `self`, returning the inner service
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call2<F, R>(&mut self, f: F) -> ResponseFuture<R>
|
||||||
|
where F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
// In this implementation, `poll_ready` is not expected to be called
|
||||||
|
// first (though, it might have been).
|
||||||
|
if self.state.reserved {
|
||||||
|
self.state.reserved = false;
|
||||||
|
} else {
|
||||||
|
// Try to reserve
|
||||||
|
if !self.state.shared.reserve() {
|
||||||
|
return ResponseFuture {
|
||||||
|
inner: None,
|
||||||
|
shared: self.state.shared.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseFuture {
|
||||||
|
inner: Some(f(self)),
|
||||||
|
shared: self.state.shared.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Service for InFlightLimit<S>
|
||||||
|
where S: Service
|
||||||
|
{
|
||||||
|
type Request = S::Request;
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = Error<S::Error>;
|
||||||
|
type Future = ResponseFuture<S::Future>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||||
|
if self.state.reserved {
|
||||||
|
return self.inner.poll_ready()
|
||||||
|
.map_err(Error::Upstream);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.shared.task.register();
|
||||||
|
|
||||||
|
if !self.state.shared.reserve() {
|
||||||
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.reserved = true;
|
||||||
|
|
||||||
|
self.inner.poll_ready()
|
||||||
|
.map_err(Error::Upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, request: Self::Request) -> Self::Future {
|
||||||
|
self.call2(|me| me.inner.call(request))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> ReadyService for InFlightLimit<S>
|
||||||
|
where S: ReadyService
|
||||||
|
{
|
||||||
|
type Request = S::Request;
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = Error<S::Error>;
|
||||||
|
type Future = ResponseFuture<S::Future>;
|
||||||
|
|
||||||
|
fn call(&mut self, request: Self::Request) -> Self::Future {
|
||||||
|
self.call2(|me| me.inner.call(request))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl ResponseFuture =====
|
||||||
|
|
||||||
|
impl<T> Future for ResponseFuture<T>
|
||||||
|
where T: Future,
|
||||||
|
{
|
||||||
|
type Item = T::Item;
|
||||||
|
type Error = Error<T::Error>;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
use futures::Async::*;
|
||||||
|
|
||||||
|
let res = match self.inner {
|
||||||
|
Some(ref mut f) => {
|
||||||
|
match f.poll() {
|
||||||
|
Ok(Ready(v)) => {
|
||||||
|
self.shared.release();
|
||||||
|
Ok(Ready(v))
|
||||||
|
}
|
||||||
|
Ok(NotReady) => {
|
||||||
|
return Ok(NotReady);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.shared.release();
|
||||||
|
Err(Error::Upstream(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(Error::NoCapacity),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drop the inner future
|
||||||
|
self.inner = None;
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for ResponseFuture<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.inner.is_some() {
|
||||||
|
self.shared.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl State =====
|
||||||
|
|
||||||
|
impl Clone for State {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
State {
|
||||||
|
shared: self.shared.clone(),
|
||||||
|
reserved: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for State {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.reserved {
|
||||||
|
self.shared.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Shared =====
|
||||||
|
|
||||||
|
impl Shared {
|
||||||
|
/// Attempts to reserve capacity for a request. Returns `true` if the
|
||||||
|
/// reservation is successful.
|
||||||
|
fn reserve(&self) -> bool {
|
||||||
|
let mut curr = self.curr.load(SeqCst);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if curr == self.max {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = self.curr.compare_and_swap(curr, curr + 1, SeqCst);
|
||||||
|
|
||||||
|
if actual == curr {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
curr = actual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release a reserved in-flight request. This is called when either the
|
||||||
|
/// request has completed OR the service that made the reservation has
|
||||||
|
/// dropped.
|
||||||
|
pub fn release(&self) {
|
||||||
|
self.curr.fetch_sub(1, SeqCst);
|
||||||
|
}
|
||||||
|
}
|
241
tower-in-flight-limit/tests/in_flight_limit.rs
Normal file
241
tower-in-flight-limit/tests/in_flight_limit.rs
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
extern crate futures;
|
||||||
|
extern crate tower;
|
||||||
|
extern crate tower_mock;
|
||||||
|
extern crate tower_in_flight_limit;
|
||||||
|
|
||||||
|
use tower_in_flight_limit::InFlightLimit;
|
||||||
|
use tower::Service;
|
||||||
|
use futures::future::{Future, poll_fn};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_service_limit_functionality_with_poll_ready() {
|
||||||
|
let (mut service, mut handle) =
|
||||||
|
new_service(2);
|
||||||
|
|
||||||
|
poll_fn(|| service.poll_ready()).wait().unwrap();
|
||||||
|
let r1 = service.call("hello 1");
|
||||||
|
|
||||||
|
poll_fn(|| service.poll_ready()).wait().unwrap();
|
||||||
|
let r2 = service.call("hello 2");
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(service.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
// The request gets passed through
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
assert_eq!(*request, "hello 1");
|
||||||
|
request.respond("world 1");
|
||||||
|
|
||||||
|
// The next request gets passed through
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
assert_eq!(*request, "hello 2");
|
||||||
|
request.respond("world 2");
|
||||||
|
|
||||||
|
// There are no more requests
|
||||||
|
with_task(|| {
|
||||||
|
assert!(handle.poll_request().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(r1.wait().unwrap(), "world 1");
|
||||||
|
|
||||||
|
// Another request can be sent
|
||||||
|
poll_fn(|| service.poll_ready()).wait().unwrap();
|
||||||
|
let r3 = service.call("hello 3");
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(service.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(r2.wait().unwrap(), "world 2");
|
||||||
|
|
||||||
|
// The request gets passed through
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
assert_eq!(*request, "hello 3");
|
||||||
|
request.respond("world 3");
|
||||||
|
|
||||||
|
assert_eq!(r3.wait().unwrap(), "world 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_service_limit_functionality_without_poll_ready() {
|
||||||
|
let (mut service, mut handle) =
|
||||||
|
new_service(2);
|
||||||
|
|
||||||
|
let r1 = service.call("hello 1");
|
||||||
|
let r2 = service.call("hello 2");
|
||||||
|
let r3 = service.call("hello 3");
|
||||||
|
r3.wait().unwrap_err();
|
||||||
|
|
||||||
|
// The request gets passed through
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
assert_eq!(*request, "hello 1");
|
||||||
|
request.respond("world 1");
|
||||||
|
|
||||||
|
// The next request gets passed through
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
assert_eq!(*request, "hello 2");
|
||||||
|
request.respond("world 2");
|
||||||
|
|
||||||
|
// There are no more requests
|
||||||
|
with_task(|| {
|
||||||
|
assert!(handle.poll_request().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(r1.wait().unwrap(), "world 1");
|
||||||
|
|
||||||
|
// One more request can be sent
|
||||||
|
let r4 = service.call("hello 4");
|
||||||
|
|
||||||
|
let r5 = service.call("hello 5");
|
||||||
|
r5.wait().unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(r2.wait().unwrap(), "world 2");
|
||||||
|
|
||||||
|
// The request gets passed through
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
assert_eq!(*request, "hello 4");
|
||||||
|
request.respond("world 4");
|
||||||
|
|
||||||
|
assert_eq!(r4.wait().unwrap(), "world 4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn request_without_capacity() {
|
||||||
|
let (mut service, mut handle) =
|
||||||
|
new_service(0);
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(service.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = service.call("hello");
|
||||||
|
|
||||||
|
// There are no more requests
|
||||||
|
with_task(|| {
|
||||||
|
assert!(handle.poll_request().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
response.wait().unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reserve_capacity_without_sending_request() {
|
||||||
|
let (mut s1, mut handle) =
|
||||||
|
new_service(1);
|
||||||
|
|
||||||
|
let mut s2 = s1.clone();
|
||||||
|
|
||||||
|
// Reserve capacity in s1
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s1.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Service 2 cannot get capacity
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
// s1 sends the request, then s2 is able to get capacity
|
||||||
|
let r1 = s1.call("hello");
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
request.respond("world");
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
r1.wait().unwrap();
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_drop_frees_capacity() {
|
||||||
|
let (mut s1, _handle) =
|
||||||
|
new_service(1);
|
||||||
|
|
||||||
|
let mut s2 = s1.clone();
|
||||||
|
|
||||||
|
// Reserve capacity in s1
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s1.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Service 2 cannot get capacity
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
drop(s1);
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_error_releases_capacity() {
|
||||||
|
let (mut s1, mut handle) =
|
||||||
|
new_service(1);
|
||||||
|
|
||||||
|
let mut s2 = s1.clone();
|
||||||
|
|
||||||
|
// Reserve capacity in s1
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s1.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
// s1 sends the request, then s2 is able to get capacity
|
||||||
|
let r1 = s1.call("hello");
|
||||||
|
let request = handle.next_request().unwrap();
|
||||||
|
request.error(());
|
||||||
|
|
||||||
|
r1.wait().unwrap_err();
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_future_drop_releases_capacity() {
|
||||||
|
let (mut s1, _handle) =
|
||||||
|
new_service(1);
|
||||||
|
|
||||||
|
let mut s2 = s1.clone();
|
||||||
|
|
||||||
|
// Reserve capacity in s1
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s1.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
// s1 sends the request, then s2 is able to get capacity
|
||||||
|
let r1 = s1.call("hello");
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_not_ready());
|
||||||
|
});
|
||||||
|
|
||||||
|
drop(r1);
|
||||||
|
|
||||||
|
with_task(|| {
|
||||||
|
assert!(s2.poll_ready().unwrap().is_ready());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mock = tower_mock::Mock<&'static str, &'static str, ()>;
|
||||||
|
type Handle = tower_mock::Handle<&'static str, &'static str, ()>;
|
||||||
|
|
||||||
|
fn new_service(max: usize) -> (InFlightLimit<Mock>, Handle) {
|
||||||
|
let (service, handle) = Mock::new();
|
||||||
|
let service = InFlightLimit::new(service, max);
|
||||||
|
(service, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_task<F: FnOnce() -> U, U>(f: F) -> U {
|
||||||
|
use futures::future::{Future, lazy};
|
||||||
|
lazy(|| Ok::<_, ()>(f())).wait().unwrap()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user