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
|
||||||
|
if self.0.state.timeout_elapsed(scope.now()) {
|
||||||
self.0.on_error(::Error::Timeout, &**scope);
|
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