diff --git a/src/client/mod.rs b/src/client/mod.rs index e7a104ff88..83706adce0 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -753,8 +753,7 @@ pub struct Builder { keep_alive_timeout: Option, h1_writev: bool, h1_title_case_headers: bool, - //TODO: make use of max_idle config - max_idle: usize, + max_idle_per_host: usize, retry_canceled_requests: bool, set_host: bool, ver: Ver, @@ -768,7 +767,7 @@ impl Default for Builder { keep_alive_timeout: Some(Duration::from_secs(90)), h1_writev: true, h1_title_case_headers: false, - max_idle: 5, + max_idle_per_host: ::std::usize::MAX, retry_canceled_requests: true, set_host: true, ver: Ver::Http1, @@ -839,6 +838,14 @@ impl Builder { self } + /// Sets the maximum idle connection per host allowed in the pool. + /// + /// Default is `usize::MAX` (no limit). + pub fn max_idle_per_host(&mut self, max_idle: usize) -> &mut Self { + self.max_idle_per_host = max_idle; + self + } + /// Set whether to retry requests that get disrupted before ever starting /// to write. /// @@ -905,7 +912,12 @@ impl Builder { executor: self.exec.clone(), h1_writev: self.h1_writev, h1_title_case_headers: self.h1_title_case_headers, - pool: Pool::new(self.keep_alive, self.keep_alive_timeout, &self.exec), + pool: Pool::new( + pool::Enabled(self.keep_alive), + pool::IdleTimeout(self.keep_alive_timeout), + pool::MaxIdlePerHost(self.max_idle_per_host), + &self.exec, + ), retry_canceled_requests: self.retry_canceled_requests, set_host: self.set_host, ver: self.ver, @@ -919,7 +931,7 @@ impl fmt::Debug for Builder { .field("keep_alive", &self.keep_alive) .field("keep_alive_timeout", &self.keep_alive_timeout) .field("http1_writev", &self.h1_writev) - .field("max_idle", &self.max_idle) + .field("max_idle_per_host", &self.max_idle_per_host) .field("set_host", &self.set_host) .field("version", &self.ver) .finish() diff --git a/src/client/pool.rs b/src/client/pool.rs index d4c4e23d70..1cccc177de 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -60,6 +60,7 @@ struct Connections { // These are internal Conns sitting in the event loop in the KeepAlive // state, waiting to receive a new Request to send on the socket. idle: HashMap>>, + max_idle_per_host: usize, // These are outstanding Checkouts that are waiting for a socket to be // able to send a Request one. This is used when "racing" for a new // connection. @@ -83,8 +84,13 @@ struct Connections { // doesn't need it! struct WeakOpt(Option>); +// Newtypes to act as keyword arguments for `Pool::new`... +pub(super) struct Enabled(pub(super) bool); +pub(super) struct IdleTimeout(pub(super) Option); +pub(super) struct MaxIdlePerHost(pub(super) usize); + impl Pool { - pub fn new(enabled: bool, timeout: Option, __exec: &Exec) -> Pool { + pub fn new(enabled: Enabled, timeout: IdleTimeout, max_idle: MaxIdlePerHost, __exec: &Exec) -> Pool { Pool { inner: Arc::new(PoolInner { connections: Mutex::new(Connections { @@ -92,12 +98,13 @@ impl Pool { idle: HashMap::new(), #[cfg(feature = "runtime")] idle_interval_ref: None, + max_idle_per_host: max_idle.0, waiters: HashMap::new(), #[cfg(feature = "runtime")] exec: __exec.clone(), - timeout, + timeout: timeout.0, }), - enabled, + enabled: enabled.0, }), } } @@ -376,13 +383,23 @@ impl Connections { match value { Some(value) => { - debug!("pooling idle connection for {:?}", key); - self.idle.entry(key) - .or_insert(Vec::new()) - .push(Idle { - value: value, - idle_at: Instant::now(), - }); + // borrow-check scope... + { + let idle_list = self + .idle + .entry(key.clone()) + .or_insert(Vec::new()); + if self.max_idle_per_host <= idle_list.len() { + trace!("max idle per host for {:?}, dropping connection", key); + return; + } + + debug!("pooling idle connection for {:?}", key); + idle_list.push(Idle { + value: value, + idle_at: Instant::now(), + }); + } #[cfg(feature = "runtime")] { @@ -773,7 +790,16 @@ mod tests { } fn pool_no_timer() -> Pool { - let pool = Pool::new(true, Some(Duration::from_millis(100)), &Exec::Default); + pool_max_idle_no_timer(::std::usize::MAX) + } + + fn pool_max_idle_no_timer(max_idle: usize) -> Pool { + let pool = Pool::new( + super::Enabled(true), + super::IdleTimeout(Some(Duration::from_millis(100))), + super::MaxIdlePerHost(max_idle), + &Exec::Default, + ); pool.no_timer(); pool } @@ -826,13 +852,35 @@ mod tests { }).wait().unwrap(); } + #[test] + fn test_pool_max_idle_per_host() { + future::lazy(|| { + let pool = pool_max_idle_no_timer(2); + let key = (Arc::new("foo".to_string()), Ver::Http1); + + pool.pooled(c(key.clone()), Uniq(41)); + pool.pooled(c(key.clone()), Uniq(5)); + pool.pooled(c(key.clone()), Uniq(99)); + + // pooled and dropped 3, max_idle should only allow 2 + assert_eq!(pool.inner.connections.lock().unwrap().idle.get(&key).map(|entries| entries.len()), Some(2)); + + Ok::<(), ()>(()) + }).wait().unwrap(); + } + #[cfg(feature = "runtime")] #[test] fn test_pool_timer_removes_expired() { use std::time::Instant; use tokio_timer::Delay; let mut rt = ::tokio::runtime::current_thread::Runtime::new().unwrap(); - let pool = Pool::new(true, Some(Duration::from_millis(100)), &Exec::Default); + let pool = Pool::new( + super::Enabled(true), + super::IdleTimeout(Some(Duration::from_millis(100))), + super::MaxIdlePerHost(::std::usize::MAX), + &Exec::Default, + ); let key = (Arc::new("foo".to_string()), Ver::Http1);