fix(http): Connection checks for spurious timeouts
We've been seeing a strange number of timeouts in our benchmarking. Handling spurious timeouts as in this patch seems to fix it! Note that managing the `timeout_start` needs to be done carefully. If the current time is provided in the wrong place, it's possible requests would never timeout.
This commit is contained in:
		| @@ -493,8 +493,13 @@ where C: Connect, | |||||||
|                         trace!("connected and writable {:?}", seed.0); |                         trace!("connected and writable {:?}", seed.0); | ||||||
|                         rotor::Response::ok( |                         rotor::Response::ok( | ||||||
|                             ClientFsm::Socket( |                             ClientFsm::Socket( | ||||||
|                                 http::Conn::new(seed.0, seed.1, Next::write().timeout(scope.connect_timeout), scope.notifier()) |                                 http::Conn::new( | ||||||
|                                     .keep_alive(scope.keep_alive) |                                     seed.0, | ||||||
|  |                                     seed.1, | ||||||
|  |                                     Next::write().timeout(scope.connect_timeout), | ||||||
|  |                                     scope.notifier(), | ||||||
|  |                                     scope.now() | ||||||
|  |                                 ).keep_alive(scope.keep_alive) | ||||||
|                             ) |                             ) | ||||||
|                         ) |                         ) | ||||||
|                     } else { |                     } else { | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ use std::marker::PhantomData; | |||||||
| use std::mem; | use std::mem; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| use rotor::{self, EventSet, PollOpt, Scope}; | use rotor::{self, EventSet, PollOpt, Scope, Time}; | ||||||
|  |  | ||||||
| use http::{self, h1, Http1Message, Encoder, Decoder, Next, Next_, Reg, Control}; | use http::{self, h1, Http1Message, Encoder, Decoder, Next, Next_, Reg, Control}; | ||||||
| use http::channel; | use http::channel; | ||||||
| @@ -176,6 +176,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                         let next = handler.on_incoming(head, &self.transport); |                         let next = handler.on_incoming(head, &self.transport); | ||||||
|                         trace!("handler.on_incoming() -> {:?}", next); |                         trace!("handler.on_incoming() -> {:?}", next); | ||||||
|  |  | ||||||
|  |                         let now = scope.now(); | ||||||
|                         match next.interest { |                         match next.interest { | ||||||
|                             Next_::Read => self.read(scope, State::Http1(Http1 { |                             Next_::Read => self.read(scope, State::Http1(Http1 { | ||||||
|                                 handler: handler, |                                 handler: handler, | ||||||
| @@ -183,6 +184,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                                 writing: Writing::Init, |                                 writing: Writing::Init, | ||||||
|                                 keep_alive: keep_alive, |                                 keep_alive: keep_alive, | ||||||
|                                 timeout: next.timeout, |                                 timeout: next.timeout, | ||||||
|  |                                 timeout_start: Some(now), | ||||||
|                                 _marker: PhantomData, |                                 _marker: PhantomData, | ||||||
|                             })), |                             })), | ||||||
|                             Next_::Write => State::Http1(Http1 { |                             Next_::Write => State::Http1(Http1 { | ||||||
| @@ -199,6 +201,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                                 writing: Writing::Head, |                                 writing: Writing::Head, | ||||||
|                                 keep_alive: keep_alive, |                                 keep_alive: keep_alive, | ||||||
|                                 timeout: next.timeout, |                                 timeout: next.timeout, | ||||||
|  |                                 timeout_start: Some(now), | ||||||
|                                 _marker: PhantomData, |                                 _marker: PhantomData, | ||||||
|                             }), |                             }), | ||||||
|                             Next_::ReadWrite => self.read(scope, State::Http1(Http1 { |                             Next_::ReadWrite => self.read(scope, State::Http1(Http1 { | ||||||
| @@ -207,6 +210,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                                 writing: Writing::Head, |                                 writing: Writing::Head, | ||||||
|                                 keep_alive: keep_alive, |                                 keep_alive: keep_alive, | ||||||
|                                 timeout: next.timeout, |                                 timeout: next.timeout, | ||||||
|  |                                 timeout_start: Some(now), | ||||||
|                                 _marker: PhantomData, |                                 _marker: PhantomData, | ||||||
|                             })), |                             })), | ||||||
|                             Next_::Wait => State::Http1(Http1 { |                             Next_::Wait => State::Http1(Http1 { | ||||||
| @@ -215,6 +219,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                                 writing: Writing::Init, |                                 writing: Writing::Init, | ||||||
|                                 keep_alive: keep_alive, |                                 keep_alive: keep_alive, | ||||||
|                                 timeout: next.timeout, |                                 timeout: next.timeout, | ||||||
|  |                                 timeout_start: Some(now), | ||||||
|                                 _marker: PhantomData, |                                 _marker: PhantomData, | ||||||
|                             }), |                             }), | ||||||
|                             Next_::End | |                             Next_::End | | ||||||
| @@ -288,7 +293,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                 }; |                 }; | ||||||
|                 let mut s = State::Http1(http1); |                 let mut s = State::Http1(http1); | ||||||
|                 if let Some(next) = next { |                 if let Some(next) = next { | ||||||
|                     s.update(next, &**scope); |                     s.update(next, &**scope, Some(scope.now())); | ||||||
|                 } |                 } | ||||||
|                 trace!("Conn.on_readable State::Http1 completed, new state = State::{:?}", s); |                 trace!("Conn.on_readable State::Http1 completed, new state = State::{:?}", s); | ||||||
|  |  | ||||||
| @@ -354,6 +359,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|                         handler: handler, |                         handler: handler, | ||||||
|                         keep_alive: keep_alive, |                         keep_alive: keep_alive, | ||||||
|                         timeout: interest.timeout, |                         timeout: interest.timeout, | ||||||
|  |                         timeout_start: Some(scope.now()), | ||||||
|                         _marker: PhantomData, |                         _marker: PhantomData, | ||||||
|                     }) |                     }) | ||||||
|                 } |                 } | ||||||
| @@ -447,7 +453,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         if let Some(next) = next { |         if let Some(next) = next { | ||||||
|             state.update(next, &**scope); |             state.update(next, &**scope, Some(scope.now())); | ||||||
|         } |         } | ||||||
|         state |         state | ||||||
|     } |     } | ||||||
| @@ -467,7 +473,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> ConnInner<K, T, H> { | |||||||
|             State::Http1(ref mut http1) => http1.handler.on_error(err), |             State::Http1(ref mut http1) => http1.handler.on_error(err), | ||||||
|             State::Closed => Next::remove(), |             State::Closed => Next::remove(), | ||||||
|         }; |         }; | ||||||
|         self.state.update(next, factory); |         self.state.update(next, factory, None); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn on_readable<F>(&mut self, scope: &mut Scope<F>) |     fn on_readable<F>(&mut self, scope: &mut Scope<F>) | ||||||
| @@ -502,7 +508,13 @@ pub enum ReadyResult<C> { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl<K: Key, T: Transport, H: MessageHandler<T>> Conn<K, T, H> { | impl<K: Key, T: Transport, H: MessageHandler<T>> Conn<K, T, H> { | ||||||
|     pub fn new(key: K, transport: T, next: Next, notify: rotor::Notifier) -> Conn<K, T, H> { |     pub fn new( | ||||||
|  |         key: K, | ||||||
|  |         transport: T, | ||||||
|  |         next: Next, | ||||||
|  |         notify: rotor::Notifier, | ||||||
|  |         now: Time | ||||||
|  |     ) -> Conn<K, T, H> { | ||||||
|         Conn(Box::new(ConnInner { |         Conn(Box::new(ConnInner { | ||||||
|             buf: Buffer::new(), |             buf: Buffer::new(), | ||||||
|             ctrl: channel::new(notify), |             ctrl: channel::new(notify), | ||||||
| @@ -511,6 +523,7 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> Conn<K, T, H> { | |||||||
|             state: State::Init { |             state: State::Init { | ||||||
|                 interest: next.interest, |                 interest: next.interest, | ||||||
|                 timeout: next.timeout, |                 timeout: next.timeout, | ||||||
|  |                 timeout_start: Some(now), | ||||||
|             }, |             }, | ||||||
|             transport: transport, |             transport: transport, | ||||||
|         })) |         })) | ||||||
| @@ -615,7 +628,8 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> Conn<K, T, H> { | |||||||
|     where F: MessageHandlerFactory<K, T, Output=H> { |     where F: MessageHandlerFactory<K, T, Output=H> { | ||||||
|         while let Ok(next) = self.0.ctrl.1.try_recv() { |         while let Ok(next) = self.0.ctrl.1.try_recv() { | ||||||
|             trace!("woke up with {:?}", next); |             trace!("woke up with {:?}", next); | ||||||
|             self.0.state.update(next, &**scope); |             let timeout_start = self.0.state.timeout_start(); | ||||||
|  |             self.0.state.update(next, &**scope, timeout_start); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let mut conn = Some(self); |         let mut conn = Some(self); | ||||||
| @@ -629,8 +643,11 @@ impl<K: Key, T: Transport, H: MessageHandler<T>> Conn<K, T, H> { | |||||||
|  |  | ||||||
|     pub fn timeout<F>(mut self, scope: &mut Scope<F>) -> Option<(Self, Option<Duration>)> |     pub fn timeout<F>(mut self, scope: &mut Scope<F>) -> Option<(Self, Option<Duration>)> | ||||||
|     where F: MessageHandlerFactory<K, T, Output=H> { |     where F: MessageHandlerFactory<K, T, Output=H> { | ||||||
|         //TODO: check if this was a spurious timeout? |         // Run error handler if timeout has elapsed | ||||||
|         self.0.on_error(::Error::Timeout, &**scope); |         if self.0.state.timeout_elapsed(scope.now()) { | ||||||
|  |             self.0.on_error(::Error::Timeout, &**scope); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         let mut conn = Some(self); |         let mut conn = Some(self); | ||||||
|         loop { |         loop { | ||||||
|             match conn.take().unwrap().ready(EventSet::none(), scope) { |             match conn.take().unwrap().ready(EventSet::none(), scope) { | ||||||
| @@ -667,6 +684,7 @@ enum State<H: MessageHandler<T>, T: Transport> { | |||||||
|     Init { |     Init { | ||||||
|         interest: Next_, |         interest: Next_, | ||||||
|         timeout: Option<Duration>, |         timeout: Option<Duration>, | ||||||
|  |         timeout_start: Option<Time>, | ||||||
|     }, |     }, | ||||||
|     /// Http1 will only ever use a connection to send and receive a single |     /// Http1 will only ever use a connection to send and receive a single | ||||||
|     /// message at a time. Once a H1 status has been determined, we will either |     /// message at a time. Once a H1 status has been determined, we will either | ||||||
| @@ -681,6 +699,27 @@ enum State<H: MessageHandler<T>, T: Transport> { | |||||||
|     Closed, |     Closed, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Given two rotor::Time and a duration, see if the duration has elapsed. | ||||||
|  | /// | ||||||
|  | /// The rotor::Time type only implements Add<Duration>, doesn't provide an API for comparing | ||||||
|  | /// itself with other rotor::Time, and it doesn't implement arithmetic operations with itself. | ||||||
|  | /// | ||||||
|  | /// `Time` is just a newtype around (u64). Since there's no other way to compare them, we'll just | ||||||
|  | /// use this knowledge to actually do a comparison. | ||||||
|  | fn timeout_elapsed(timeout: Duration, start: Time, now: Time) -> bool { | ||||||
|  |     // type annotation for sanity | ||||||
|  |     let timeout_at: rotor::Time = start + timeout; | ||||||
|  |  | ||||||
|  |     let timeout_at: u64 = unsafe { mem::transmute(timeout_at) }; | ||||||
|  |     let now: u64 = unsafe { mem::transmute(now) }; | ||||||
|  |  | ||||||
|  |     if now >= timeout_at { | ||||||
|  |         true | ||||||
|  |     } else { | ||||||
|  |         false | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| impl<H: MessageHandler<T>, T: Transport> State<H, T> { | impl<H: MessageHandler<T>, T: Transport> State<H, T> { | ||||||
|     fn timeout(&self) -> Option<Duration> { |     fn timeout(&self) -> Option<Duration> { | ||||||
| @@ -690,14 +729,37 @@ impl<H: MessageHandler<T>, T: Transport> State<H, T> { | |||||||
|             State::Closed => None, |             State::Closed => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn timeout_start(&self) -> Option<Time> { | ||||||
|  |         match *self { | ||||||
|  |             State::Init { timeout_start, .. } => timeout_start, | ||||||
|  |             State::Http1(ref http1) => http1.timeout_start, | ||||||
|  |             State::Closed => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn timeout_elapsed(&self, now: Time) -> bool { | ||||||
|  |         match *self { | ||||||
|  |             State::Init { timeout, timeout_start, .. } => { | ||||||
|  |                 if let (Some(timeout), Some(start)) = (timeout, timeout_start) { | ||||||
|  |                     timeout_elapsed(timeout, start, now) | ||||||
|  |                 } else { | ||||||
|  |                     false | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             State::Http1(ref http1) => http1.timeout_elapsed(now), | ||||||
|  |             State::Closed => false, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<H: MessageHandler<T>, T: Transport> fmt::Debug for State<H, T> { | impl<H: MessageHandler<T>, T: Transport> fmt::Debug for State<H, T> { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         match *self { |         match *self { | ||||||
|             State::Init { interest, timeout } => f.debug_struct("Init") |             State::Init { interest, timeout, timeout_start } => f.debug_struct("Init") | ||||||
|                 .field("interest", &interest) |                 .field("interest", &interest) | ||||||
|                 .field("timeout", &timeout) |                 .field("timeout", &timeout) | ||||||
|  |                 .field("timeout_start", &timeout_start) | ||||||
|                 .finish(), |                 .finish(), | ||||||
|             State::Http1(ref h1) => f.debug_tuple("Http1") |             State::Http1(ref h1) => f.debug_tuple("Http1") | ||||||
|                 .field(h1) |                 .field(h1) | ||||||
| @@ -708,7 +770,7 @@ impl<H: MessageHandler<T>, T: Transport> fmt::Debug for State<H, T> { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl<H: MessageHandler<T>, T: Transport> State<H, T> { | impl<H: MessageHandler<T>, T: Transport> State<H, T> { | ||||||
|     fn update<F, K>(&mut self, next: Next, factory: &F) |     fn update<F, K>(&mut self, next: Next, factory: &F, timeout_start: Option<Time>) | ||||||
|             where F: MessageHandlerFactory<K, T>, |             where F: MessageHandlerFactory<K, T>, | ||||||
|                   K: Key |                   K: Key | ||||||
|         { |         { | ||||||
| @@ -723,6 +785,7 @@ impl<H: MessageHandler<T>, T: Transport> State<H, T> { | |||||||
|                                  State::Init { |                                  State::Init { | ||||||
|                                      interest: e, |                                      interest: e, | ||||||
|                                      timeout: timeout, |                                      timeout: timeout, | ||||||
|  |                                      timeout_start: timeout_start, | ||||||
|                                  }); |                                  }); | ||||||
|                 } |                 } | ||||||
|                 (State::Http1(mut http1), next_) => { |                 (State::Http1(mut http1), next_) => { | ||||||
| @@ -782,6 +845,7 @@ impl<H: MessageHandler<T>, T: Transport> State<H, T> { | |||||||
|                                                  State::Init { |                                                  State::Init { | ||||||
|                                                      interest: next.interest, |                                                      interest: next.interest, | ||||||
|                                                      timeout: next.timeout, |                                                      timeout: next.timeout, | ||||||
|  |                                                      timeout_start: timeout_start, | ||||||
|                                                  }); |                                                  }); | ||||||
|                                     return; |                                     return; | ||||||
|                                 } |                                 } | ||||||
| @@ -913,9 +977,20 @@ struct Http1<H, T> { | |||||||
|     writing: Writing, |     writing: Writing, | ||||||
|     keep_alive: bool, |     keep_alive: bool, | ||||||
|     timeout: Option<Duration>, |     timeout: Option<Duration>, | ||||||
|  |     timeout_start: Option<Time>, | ||||||
|     _marker: PhantomData<T>, |     _marker: PhantomData<T>, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl<H, T> Http1<H, T> { | ||||||
|  |     fn timeout_elapsed(&self, now: Time) -> bool { | ||||||
|  |         if let (Some(timeout), Some(start)) = (self.timeout, self.timeout_start) { | ||||||
|  |             timeout_elapsed(timeout, start, now) | ||||||
|  |         } else { | ||||||
|  |             false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl<H, T> fmt::Debug for Http1<H, T> { | impl<H, T> fmt::Debug for Http1<H, T> { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         f.debug_struct("Http1") |         f.debug_struct("Http1") | ||||||
|   | |||||||
| @@ -226,7 +226,7 @@ where A: Accept, | |||||||
|         rotor_try!(scope.register(&seed, EventSet::readable(), PollOpt::level())); |         rotor_try!(scope.register(&seed, EventSet::readable(), PollOpt::level())); | ||||||
|         rotor::Response::ok( |         rotor::Response::ok( | ||||||
|             ServerFsm::Conn( |             ServerFsm::Conn( | ||||||
|                 http::Conn::new((), seed, Next::read(), scope.notifier()) |                 http::Conn::new((), seed, Next::read(), scope.notifier(), scope.now()) | ||||||
|                     .keep_alive(scope.keep_alive) |                     .keep_alive(scope.keep_alive) | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user