nghttpx: Robust backend read timeout

This commit is contained in:
Tatsuhiro Tsujikawa 2016-11-26 00:00:32 +09:00
parent d83949bc88
commit e9ab75a386
5 changed files with 86 additions and 26 deletions

View File

@ -67,7 +67,9 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
fd(fd), fd(fd),
tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold), tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold),
tls_dyn_rec_idle_timeout(tls_dyn_rec_idle_timeout), tls_dyn_rec_idle_timeout(tls_dyn_rec_idle_timeout),
proto(proto) { proto(proto),
last_read(0.),
read_timeout(read_timeout) {
ev_io_init(&wev, writecb, fd, EV_WRITE); ev_io_init(&wev, writecb, fd, EV_WRITE);
ev_io_init(&rev, readcb, fd, EV_READ); ev_io_init(&rev, readcb, fd, EV_READ);
@ -809,4 +811,27 @@ int Connection::get_tcp_hint(TCPHint *hint) const {
#endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) #endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT)
} }
void Connection::again_rt(ev_tstamp t) {
read_timeout = t;
rt.repeat = t;
ev_timer_again(loop, &rt);
last_read = ev_now(loop);
}
void Connection::again_rt() {
rt.repeat = read_timeout;
ev_timer_again(loop, &rt);
last_read = ev_now(loop);
}
bool Connection::expired_rt() {
auto delta = read_timeout - (ev_now(loop) - last_read);
if (delta < 1e-9) {
return true;
}
rt.repeat = delta;
ev_timer_again(loop, &rt);
return false;
}
} // namespace shrpx } // namespace shrpx

View File

@ -125,6 +125,17 @@ struct Connection {
int get_tcp_hint(TCPHint *hint) const; int get_tcp_hint(TCPHint *hint) const;
// These functions are provided for read timer which is frequently
// restarted. We do a trick to make a bit more efficient than just
// calling ev_timer_again().
// Restarts read timer with timeout value |t|.
void again_rt(ev_tstamp t);
// Restarts read timer without chainging timeout.
void again_rt();
// Returns true if read timer expired.
bool expired_rt();
TLSConnection tls; TLSConnection tls;
ev_io wev; ev_io wev;
ev_io rev; ev_io rev;
@ -141,6 +152,11 @@ struct Connection {
// used in this object at the moment. The rest of the program may // used in this object at the moment. The rest of the program may
// use this value when it is useful. // use this value when it is useful.
shrpx_proto proto; shrpx_proto proto;
// The point of time when last read is observed. Note: sinde we use
// |rt| as idle timer, the activity is not limited to read.
ev_tstamp last_read;
// Timeout for read timer |rt|.
ev_tstamp read_timeout;
}; };
// Creates BIO_method shared by all SSL objects. If nghttp2 is built // Creates BIO_method shared by all SSL objects. If nghttp2 is built

View File

@ -111,6 +111,10 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto conn = static_cast<Connection *>(w->data); auto conn = static_cast<Connection *>(w->data);
auto http2session = static_cast<Http2Session *>(conn->data); auto http2session = static_cast<Http2Session *>(conn->data);
if (w == &conn->rt && !conn->expired_rt()) {
return;
}
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session) << "Timeout"; SSLOG(INFO, http2session) << "Timeout";
} }
@ -491,10 +495,7 @@ int Http2Session::initiate_connection() {
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
} else { } else {
conn_.rlimit.startw(); conn_.rlimit.startw();
conn_.again_rt();
if (addr_->num_dconn == 0) {
ev_timer_again(conn_.loop, &conn_.rt);
}
} }
return 0; return 0;
@ -615,8 +616,6 @@ int Http2Session::downstream_connect_proxy() {
void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) { void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) {
dconns_.append(dconn); dconns_.append(dconn);
++addr_->num_dconn; ++addr_->num_dconn;
stop_read_timer();
} }
void Http2Session::remove_downstream_connection( void Http2Session::remove_downstream_connection(
@ -625,10 +624,6 @@ void Http2Session::remove_downstream_connection(
dconns_.remove(dconn); dconns_.remove(dconn);
dconn->detach_stream_data(); dconn->detach_stream_data();
if (addr_->num_dconn == 0) {
repeat_read_timer();
}
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Remove downstream"; SSLOG(INFO, this) << "Remove downstream";
} }
@ -1867,6 +1862,7 @@ int Http2Session::connected() {
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
conn_.rlimit.startw(); conn_.rlimit.startw();
conn_.again_rt();
read_ = &Http2Session::read_clear; read_ = &Http2Session::read_clear;
write_ = &Http2Session::write_clear; write_ = &Http2Session::write_clear;
@ -1891,6 +1887,8 @@ int Http2Session::connected() {
} }
int Http2Session::read_clear() { int Http2Session::read_clear() {
conn_.last_read = ev_now(conn_.loop);
std::array<uint8_t, 16_k> buf; std::array<uint8_t, 16_k> buf;
for (;;) { for (;;) {
@ -1911,6 +1909,8 @@ int Http2Session::read_clear() {
} }
int Http2Session::write_clear() { int Http2Session::write_clear() {
conn_.last_read = ev_now(conn_.loop);
std::array<struct iovec, MAX_WR_IOVCNT> iov; std::array<struct iovec, MAX_WR_IOVCNT> iov;
for (;;) { for (;;) {
@ -1945,7 +1945,7 @@ int Http2Session::write_clear() {
} }
int Http2Session::tls_handshake() { int Http2Session::tls_handshake() {
ev_timer_again(conn_.loop, &conn_.rt); conn_.last_read = ev_now(conn_.loop);
ERR_clear_error(); ERR_clear_error();
@ -1992,6 +1992,8 @@ int Http2Session::tls_handshake() {
} }
int Http2Session::read_tls() { int Http2Session::read_tls() {
conn_.last_read = ev_now(conn_.loop);
std::array<uint8_t, 16_k> buf; std::array<uint8_t, 16_k> buf;
ERR_clear_error(); ERR_clear_error();
@ -2014,6 +2016,8 @@ int Http2Session::read_tls() {
} }
int Http2Session::write_tls() { int Http2Session::write_tls() {
conn_.last_read = ev_now(conn_.loop);
ERR_clear_error(); ERR_clear_error();
struct iovec iov; struct iovec iov;
@ -2281,10 +2285,4 @@ void Http2Session::check_retire() {
signal_write(); signal_write();
} }
void Http2Session::repeat_read_timer() {
ev_timer_again(conn_.loop, &conn_.rt);
}
void Http2Session::stop_read_timer() { ev_timer_stop(conn_.loop, &conn_.rt); }
} // namespace shrpx } // namespace shrpx

View File

@ -203,9 +203,6 @@ public:
// shutdown the connection. // shutdown the connection.
void check_retire(); void check_retire();
void repeat_read_timer();
void stop_read_timer();
enum { enum {
// Disconnected // Disconnected
DISCONNECTED, DISCONNECTED,

View File

@ -48,6 +48,10 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto conn = static_cast<Connection *>(w->data); auto conn = static_cast<Connection *>(w->data);
auto dconn = static_cast<HttpDownstreamConnection *>(conn->data); auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
if (w == &conn->rt && !conn->expired_rt()) {
return;
}
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Time out"; DCLOG(INFO, dconn) << "Time out";
} }
@ -319,9 +323,13 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
} else { } else {
// we may set read timer cb to idle_timeoutcb. Reset again. // we may set read timer cb to idle_timeoutcb. Reset again.
conn_.rt.repeat = downstreamconf.timeout.read;
ev_set_cb(&conn_.rt, timeoutcb); ev_set_cb(&conn_.rt, timeoutcb);
ev_timer_stop(conn_.loop, &conn_.rt); if (conn_.read_timeout < downstreamconf.timeout.read) {
conn_.read_timeout = downstreamconf.timeout.read;
conn_.last_read = ev_now(conn_.loop);
} else {
conn_.again_rt(downstreamconf.timeout.read);
}
ev_set_cb(&conn_.rev, readcb); ev_set_cb(&conn_.rev, readcb);
} }
@ -617,6 +625,9 @@ namespace {
void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto conn = static_cast<Connection *>(w->data); auto conn = static_cast<Connection *>(w->data);
auto dconn = static_cast<HttpDownstreamConnection *>(conn->data); auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
// We don't have to check conn->expired_rt() since we restart timer
// when connection gets idle.
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection timeout"; DCLOG(INFO, dconn) << "Idle connection timeout";
} }
@ -637,9 +648,13 @@ void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
auto &downstreamconf = *worker_->get_downstream_config(); auto &downstreamconf = *worker_->get_downstream_config();
conn_.rt.repeat = downstreamconf.timeout.idle_read;
ev_set_cb(&conn_.rt, idle_timeoutcb); ev_set_cb(&conn_.rt, idle_timeoutcb);
ev_timer_again(conn_.loop, &conn_.rt); if (conn_.read_timeout < downstreamconf.timeout.idle_read) {
conn_.read_timeout = downstreamconf.timeout.idle_read;
conn_.last_read = ev_now(conn_.loop);
} else {
conn_.again_rt(downstreamconf.timeout.idle_read);
}
conn_.wlimit.stopw(); conn_.wlimit.stopw();
ev_timer_stop(conn_.loop, &conn_.wt); ev_timer_stop(conn_.loop, &conn_.wt);
@ -924,6 +939,8 @@ http_parser_settings htp_hooks = {
} // namespace } // namespace
int HttpDownstreamConnection::read_clear() { int HttpDownstreamConnection::read_clear() {
conn_.last_read = ev_now(conn_.loop);
std::array<uint8_t, 16_k> buf; std::array<uint8_t, 16_k> buf;
int rv; int rv;
@ -949,6 +966,8 @@ int HttpDownstreamConnection::read_clear() {
} }
int HttpDownstreamConnection::write_clear() { int HttpDownstreamConnection::write_clear() {
conn_.last_read = ev_now(conn_.loop);
auto upstream = downstream_->get_upstream(); auto upstream = downstream_->get_upstream();
auto input = downstream_->get_request_buf(); auto input = downstream_->get_request_buf();
@ -986,7 +1005,7 @@ int HttpDownstreamConnection::write_clear() {
int HttpDownstreamConnection::tls_handshake() { int HttpDownstreamConnection::tls_handshake() {
ERR_clear_error(); ERR_clear_error();
ev_timer_again(conn_.loop, &conn_.rt); conn_.last_read = ev_now(conn_.loop);
auto rv = conn_.tls_handshake(); auto rv = conn_.tls_handshake();
if (rv == SHRPX_ERR_INPROGRESS) { if (rv == SHRPX_ERR_INPROGRESS) {
@ -1036,6 +1055,8 @@ int HttpDownstreamConnection::tls_handshake() {
} }
int HttpDownstreamConnection::read_tls() { int HttpDownstreamConnection::read_tls() {
conn_.last_read = ev_now(conn_.loop);
ERR_clear_error(); ERR_clear_error();
std::array<uint8_t, 16_k> buf; std::array<uint8_t, 16_k> buf;
@ -1063,6 +1084,8 @@ int HttpDownstreamConnection::read_tls() {
} }
int HttpDownstreamConnection::write_tls() { int HttpDownstreamConnection::write_tls() {
conn_.last_read = ev_now(conn_.loop);
ERR_clear_error(); ERR_clear_error();
auto upstream = downstream_->get_upstream(); auto upstream = downstream_->get_upstream();
@ -1193,6 +1216,7 @@ int HttpDownstreamConnection::connected() {
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
conn_.rlimit.startw(); conn_.rlimit.startw();
conn_.again_rt();
ev_set_cb(&conn_.wev, writecb); ev_set_cb(&conn_.wev, writecb);