fix(client): schedule interval to clear expired idle connections
Currently only works if Client is built with a `Handle`, and not a custome executor, since a `Handle` is required to create a tokio Interval.
This commit is contained in:
		| @@ -90,33 +90,15 @@ impl<C, B> Client<C, B> { | |||||||
|             Exec::Executor(..) => panic!("Client not built with a Handle"), |             Exec::Executor(..) => panic!("Client not built with a Handle"), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| impl<C, B> Client<C, B> |  | ||||||
| where C: Connect, |  | ||||||
|       B: Stream<Error=::Error>, |  | ||||||
|       B::Item: AsRef<[u8]>, |  | ||||||
| { |  | ||||||
|     // Create a new client with a specific connector. |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn configured(config: Config<C, B>, exec: Exec) -> Client<C, B> { |     fn configured(config: Config<C, B>, exec: Exec) -> Client<C, B> { | ||||||
|         let client = Client { |         Client { | ||||||
|             connector: Rc::new(config.connector), |             connector: Rc::new(config.connector), | ||||||
|             executor: exec, |             executor: exec, | ||||||
|             h1_writev: config.h1_writev, |             h1_writev: config.h1_writev, | ||||||
|             pool: Pool::new(config.keep_alive, config.keep_alive_timeout), |             pool: Pool::new(config.keep_alive, config.keep_alive_timeout), | ||||||
|             retry_canceled_requests: config.retry_canceled_requests, |             retry_canceled_requests: config.retry_canceled_requests, | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         client.schedule_pool_timer(); |  | ||||||
|  |  | ||||||
|         client |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn schedule_pool_timer(&self) { |  | ||||||
|         if let Exec::Handle(ref h) = self.executor { |  | ||||||
|             self.pool.spawn_expired_interval(h); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -135,6 +117,14 @@ where C: Connect, | |||||||
|     /// Send a constructed Request using this Client. |     /// Send a constructed Request using this Client. | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn request(&self, mut req: Request<B>) -> FutureResponse { |     pub fn request(&self, mut req: Request<B>) -> FutureResponse { | ||||||
|  |         // TODO(0.12): do this at construction time. | ||||||
|  |         // | ||||||
|  |         // It cannot be done in the constructor because the Client::configured | ||||||
|  |         // does not have `B: 'static` bounds, which are required to spawn | ||||||
|  |         // the interval. In 0.12, add a static bounds to the constructor, | ||||||
|  |         // and move this. | ||||||
|  |         self.schedule_pool_timer(); | ||||||
|  |  | ||||||
|         match req.version() { |         match req.version() { | ||||||
|             HttpVersion::Http10 | |             HttpVersion::Http10 | | ||||||
|             HttpVersion::Http11 => (), |             HttpVersion::Http11 => (), | ||||||
| @@ -263,6 +253,12 @@ where C: Connect, | |||||||
|  |  | ||||||
|         Box::new(resp) |         Box::new(resp) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn schedule_pool_timer(&self) { | ||||||
|  |         if let Exec::Handle(ref h) = self.executor { | ||||||
|  |             self.pool.spawn_expired_interval(h); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<C, B> Service for Client<C, B> | impl<C, B> Service for Client<C, B> | ||||||
|   | |||||||
| @@ -41,6 +41,12 @@ struct PoolInner<T> { | |||||||
|     // connection. |     // connection. | ||||||
|     parked: HashMap<Rc<String>, VecDeque<relay::Sender<Entry<T>>>>, |     parked: HashMap<Rc<String>, VecDeque<relay::Sender<Entry<T>>>>, | ||||||
|     timeout: Option<Duration>, |     timeout: Option<Duration>, | ||||||
|  |     // Used to prevent multiple intervals from being spawned to clear | ||||||
|  |     // expired connections. | ||||||
|  |     // | ||||||
|  |     // TODO(0.12): Remove the need for this when Client::schedule_pool_timer | ||||||
|  |     // can be done in Client::new. | ||||||
|  |     expired_timer_spawned: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<T: Clone + Ready> Pool<T> { | impl<T: Clone + Ready> Pool<T> { | ||||||
| @@ -51,6 +57,7 @@ impl<T: Clone + Ready> Pool<T> { | |||||||
|                 idle: HashMap::new(), |                 idle: HashMap::new(), | ||||||
|                 parked: HashMap::new(), |                 parked: HashMap::new(), | ||||||
|                 timeout: timeout, |                 timeout: timeout, | ||||||
|  |                 expired_timer_spawned: false, | ||||||
|             })), |             })), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -229,12 +236,17 @@ impl<T> Pool<T> { | |||||||
|  |  | ||||||
| impl<T: 'static> Pool<T> { | impl<T: 'static> Pool<T> { | ||||||
|     pub(super) fn spawn_expired_interval(&self, handle: &Handle) { |     pub(super) fn spawn_expired_interval(&self, handle: &Handle) { | ||||||
|         let inner = self.inner.borrow(); |         let mut inner = self.inner.borrow_mut(); | ||||||
|  |  | ||||||
|         if !inner.enabled { |         if !inner.enabled { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if inner.expired_timer_spawned { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         inner.expired_timer_spawned = true; | ||||||
|  |  | ||||||
|         let dur = if let Some(dur) = inner.timeout { |         let dur = if let Some(dur) = inner.timeout { | ||||||
|             dur |             dur | ||||||
|         } else { |         } else { | ||||||
| @@ -529,7 +541,9 @@ mod tests { | |||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_pool_timer_removes_expired() { |     fn test_pool_timer_removes_expired() { | ||||||
|         let pool = Pool::new(true, Some(Duration::from_secs(1))); |         let mut core = ::tokio::reactor::Core::new().unwrap(); | ||||||
|  |         let pool = Pool::new(true, Some(Duration::from_millis(100))); | ||||||
|  |         pool.spawn_expired_interval(&core.handle()); | ||||||
|         let key = Rc::new("foo".to_string()); |         let key = Rc::new("foo".to_string()); | ||||||
|  |  | ||||||
|         let mut pooled1 = pool.pooled(key.clone(), 41); |         let mut pooled1 = pool.pooled(key.clone(), 41); | ||||||
| @@ -540,9 +554,13 @@ mod tests { | |||||||
|         pooled3.idle(); |         pooled3.idle(); | ||||||
|  |  | ||||||
|         assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(3)); |         assert_eq!(pool.inner.borrow().idle.get(&key).map(|entries| entries.len()), Some(3)); | ||||||
|         ::std::thread::sleep(pool.inner.borrow().timeout.unwrap()); |  | ||||||
|  |  | ||||||
|         pool.clear_expired(); |         let timeout = ::tokio::reactor::Timeout::new( | ||||||
|  |             Duration::from_millis(400), // allow for too-good resolution | ||||||
|  |             &core.handle() | ||||||
|  |         ).unwrap(); | ||||||
|  |         core.run(timeout).unwrap(); | ||||||
|  |  | ||||||
|         assert!(pool.inner.borrow().idle.get(&key).is_none()); |         assert!(pool.inner.borrow().idle.get(&key).is_none()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user