Merge pull request #1235 from nghttp2/backend-conn-timeout

nghttpx: Add read/write-timeout parameters to backend option
This commit is contained in:
Tatsuhiro Tsujikawa 2018-09-30 13:17:24 +09:00 committed by GitHub
commit 9327077741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 104 additions and 21 deletions

View File

@ -1736,11 +1736,13 @@ Connections:
parameters are: "proto=<PROTO>", "tls", parameters are: "proto=<PROTO>", "tls",
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>", "sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
"affinity=<METHOD>", "dns", "redirect-if-not-tls", "affinity=<METHOD>", "dns", "redirect-if-not-tls",
"upgrade-scheme", and "mruby=<PATH>". The parameter "upgrade-scheme", "mruby=<PATH>",
consists of keyword, and optionally followed by "=" and "read-timeout=<DURATION>", and
value. For example, the parameter "proto=h2" consists "write-timeout=<DURATION>". The parameter consists of
of the keyword "proto" and value "h2". The parameter keyword, and optionally followed by "=" and value. For
"tls" consists of the keyword "tls" without value. Each example, the parameter "proto=h2" consists of the
keyword "proto" and value "h2". The parameter "tls"
consists of the keyword "tls" without value. Each
parameter is described as follows. parameter is described as follows.
The backend application protocol can be specified using The backend application protocol can be specified using
@ -1839,6 +1841,14 @@ Connections:
matched. All backends which share the same pattern must matched. All backends which share the same pattern must
have the same mruby path. have the same mruby path.
"read-timeout=<DURATION>" and "write-timeout=<DURATION>"
parameters specify the read and write timeout of the
backend connection when this pattern is matched. All
backends which share the same pattern must have the same
timeouts. If these timeouts are entirely omitted for a
pattern, --backend-read-timeout and
--backend-write-timeout are used.
Since ";" and ":" are used as delimiter, <PATTERN> must Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special not contain these characters. Since ";" has special
meaning in shell, the option value must be quoted. meaning in shell, the option value must be quoted.

View File

@ -812,6 +812,8 @@ struct DownstreamParams {
StringRef sni; StringRef sni;
StringRef mruby; StringRef mruby;
AffinityConfig affinity; AffinityConfig affinity;
ev_tstamp read_timeout;
ev_tstamp write_timeout;
size_t fall; size_t fall;
size_t rise; size_t rise;
shrpx_proto proto; shrpx_proto proto;
@ -821,6 +823,22 @@ struct DownstreamParams {
bool upgrade_scheme; bool upgrade_scheme;
}; };
namespace {
// Parses |value| of parameter named |name| as duration. This
// function returns 0 if it succeeds and the parsed value is assigned
// to |dest|, or -1.
int parse_downstream_param_duration(ev_tstamp &dest, const StringRef &name,
const StringRef &value) {
auto t = util::parse_duration_with_unit(value);
if (t == std::numeric_limits<double>::infinity()) {
LOG(ERROR) << "backend: " << name << ": bad value: '" << value << "'";
return -1;
}
dest = t;
return 0;
}
} // namespace
namespace { namespace {
// Parses downstream configuration parameter |src_params|, and stores // Parses downstream configuration parameter |src_params|, and stores
// parsed results into |out|. This function returns 0 if it succeeds, // parsed results into |out|. This function returns 0 if it succeeds,
@ -928,6 +946,18 @@ int parse_downstream_params(DownstreamParams &out,
} else if (util::istarts_with_l(param, "mruby=")) { } else if (util::istarts_with_l(param, "mruby=")) {
auto valstr = StringRef{first + str_size("mruby="), end}; auto valstr = StringRef{first + str_size("mruby="), end};
out.mruby = valstr; out.mruby = valstr;
} else if (util::istarts_with_l(param, "read-timeout=")) {
if (parse_downstream_param_duration(
out.read_timeout, StringRef::from_lit("read-timeout"),
StringRef{first + str_size("read-timeout="), end}) == -1) {
return -1;
}
} else if (util::istarts_with_l(param, "write-timeout=")) {
if (parse_downstream_param_duration(
out.write_timeout, StringRef::from_lit("write-timeout"),
StringRef{first + str_size("write-timeout="), end}) == -1) {
return -1;
}
} else if (!param.empty()) { } else if (!param.empty()) {
LOG(ERROR) << "backend: " << param << ": unknown keyword"; LOG(ERROR) << "backend: " << param << ": unknown keyword";
return -1; return -1;
@ -1065,6 +1095,29 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
return -1; return -1;
} }
} }
// All backends in the same group must have the same read/write
// timeout. If some backends do not specify read/write timeout,
// and there is at least one backend with read/write timeout, it
// is used for all backends in the group.
if (params.read_timeout > 1e-9) {
if (g.timeout.read < 1e-9) {
g.timeout.read = params.read_timeout;
} else if (fabs(g.timeout.read - params.read_timeout) > 1e-9) {
LOG(ERROR)
<< "backend: read-timeout: multiple different read-timeout "
"found in a single group";
return -1;
}
}
if (params.write_timeout > 1e-9) {
if (g.timeout.write < 1e-9) {
g.timeout.write = params.write_timeout;
} else if (fabs(g.timeout.write - params.write_timeout) > 1e-9) {
LOG(ERROR) << "backend: write-timeout: multiple different "
"write-timeout found in a single group";
return -1;
}
}
g.addrs.push_back(addr); g.addrs.push_back(addr);
continue; continue;
@ -1087,6 +1140,8 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
} }
g.redirect_if_not_tls = params.redirect_if_not_tls; g.redirect_if_not_tls = params.redirect_if_not_tls;
g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby); g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby);
g.timeout.read = params.read_timeout;
g.timeout.write = params.write_timeout;
if (pattern[0] == '*') { if (pattern[0] == '*') {
// wildcard pattern // wildcard pattern
@ -4048,6 +4103,14 @@ int configure_downstream_group(Config *config, bool http2_proxy,
return lhs.hash < rhs.hash; return lhs.hash < rhs.hash;
}); });
} }
auto &timeout = g.timeout;
if (timeout.read < 1e-9) {
timeout.read = downstreamconf.timeout.read;
}
if (timeout.write < 1e-9) {
timeout.write = downstreamconf.timeout.write;
}
} }
return 0; return 0;

View File

@ -504,6 +504,11 @@ struct DownstreamAddrGroupConfig {
// true if this group requires that client connection must be TLS, // true if this group requires that client connection must be TLS,
// and the request must be redirected to https URI. // and the request must be redirected to https URI.
bool redirect_if_not_tls; bool redirect_if_not_tls;
// Timeouts for backend connection.
struct {
ev_tstamp read;
ev_tstamp write;
} timeout;
}; };
struct TicketKey { struct TicketKey {

View File

@ -186,9 +186,9 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
: dlnext(nullptr), : dlnext(nullptr),
dlprev(nullptr), dlprev(nullptr),
conn_(loop, -1, nullptr, worker->get_mcpool(), conn_(loop, -1, nullptr, worker->get_mcpool(),
worker->get_downstream_config()->timeout.write, group->shared_addr->timeout.write, group->shared_addr->timeout.read,
worker->get_downstream_config()->timeout.read, {}, {}, writecb, {}, {}, writecb, readcb, timeoutcb, this,
readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP2), get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP2),
wb_(worker->get_mcpool()), wb_(worker->get_mcpool()),
worker_(worker), worker_(worker),
@ -1972,10 +1972,8 @@ int Http2Session::connected() {
SSLOG(INFO, this) << "Connection established"; SSLOG(INFO, this) << "Connection established";
} }
auto &downstreamconf = *get_config()->conn.downstream;
// Reset timeout for write. Previously, we set timeout for connect. // Reset timeout for write. Previously, we set timeout for connect.
conn_.wt.repeat = downstreamconf.timeout.write; conn_.wt.repeat = group_->shared_addr->timeout.write;
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
conn_.rlimit.startw(); conn_.rlimit.startw();

View File

@ -190,9 +190,8 @@ HttpDownstreamConnection::HttpDownstreamConnection(
const std::shared_ptr<DownstreamAddrGroup> &group, size_t initial_addr_idx, const std::shared_ptr<DownstreamAddrGroup> &group, size_t initial_addr_idx,
struct ev_loop *loop, Worker *worker) struct ev_loop *loop, Worker *worker)
: conn_(loop, -1, nullptr, worker->get_mcpool(), : conn_(loop, -1, nullptr, worker->get_mcpool(),
worker->get_downstream_config()->timeout.write, group->shared_addr->timeout.write, group->shared_addr->timeout.read,
worker->get_downstream_config()->timeout.read, {}, {}, connectcb, {}, {}, connectcb, readcb, connect_timeoutcb, this,
readcb, connect_timeoutcb, this,
get_config()->tls.dyn_rec.warmup_threshold, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP1), get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP1),
on_read_(&HttpDownstreamConnection::noop), on_read_(&HttpDownstreamConnection::noop),
@ -459,11 +458,11 @@ int HttpDownstreamConnection::initiate_connection() {
} else { } else {
// we may set read timer cb to idle_timeoutcb. Reset again. // we may set read timer cb to idle_timeoutcb. Reset again.
ev_set_cb(&conn_.rt, timeoutcb); ev_set_cb(&conn_.rt, timeoutcb);
if (conn_.read_timeout < downstreamconf.timeout.read) { if (conn_.read_timeout < group_->shared_addr->timeout.read) {
conn_.read_timeout = downstreamconf.timeout.read; conn_.read_timeout = group_->shared_addr->timeout.read;
conn_.last_read = ev_now(conn_.loop); conn_.last_read = ev_now(conn_.loop);
} else { } else {
conn_.again_rt(downstreamconf.timeout.read); conn_.again_rt(group_->shared_addr->timeout.read);
} }
ev_set_cb(&conn_.rev, readcb); ev_set_cb(&conn_.rev, readcb);
@ -1489,10 +1488,8 @@ int HttpDownstreamConnection::connected() {
DCLOG(INFO, this) << "Connected to downstream host"; DCLOG(INFO, this) << "Connected to downstream host";
} }
auto &downstreamconf = *get_config()->conn.downstream;
// Reset timeout for write. Previously, we set timeout for connect. // Reset timeout for write. Previously, we set timeout for connect.
conn_.wt.repeat = downstreamconf.timeout.write; conn_.wt.repeat = group_->shared_addr->timeout.write;
ev_timer_again(conn_.loop, &conn_.wt); ev_timer_again(conn_.loop, &conn_.wt);
conn_.rlimit.startw(); conn_.rlimit.startw();

View File

@ -77,7 +77,7 @@ DownstreamAddrGroup::~DownstreamAddrGroup() {}
using DownstreamKey = std::tuple< using DownstreamKey = std::tuple<
std::vector<std::tuple<StringRef, StringRef, size_t, size_t, shrpx_proto, std::vector<std::tuple<StringRef, StringRef, size_t, size_t, shrpx_proto,
uint16_t, bool, bool, bool, bool>>, uint16_t, bool, bool, bool, bool>>,
bool, int, StringRef, StringRef, int>; bool, int, StringRef, StringRef, int, int64_t, int64_t>;
namespace { namespace {
DownstreamKey create_downstream_key( DownstreamKey create_downstream_key(
@ -109,6 +109,9 @@ DownstreamKey create_downstream_key(
std::get<3>(dkey) = affinity.cookie.name; std::get<3>(dkey) = affinity.cookie.name;
std::get<4>(dkey) = affinity.cookie.path; std::get<4>(dkey) = affinity.cookie.path;
std::get<5>(dkey) = affinity.cookie.secure; std::get<5>(dkey) = affinity.cookie.secure;
auto &timeout = shared_addr->timeout;
std::get<6>(dkey) = timeout.read;
std::get<7>(dkey) = timeout.write;
return dkey; return dkey;
} }
@ -221,6 +224,8 @@ void Worker::replace_downstream_config(
} }
shared_addr->affinity_hash = src.affinity_hash; shared_addr->affinity_hash = src.affinity_hash;
shared_addr->redirect_if_not_tls = src.redirect_if_not_tls; shared_addr->redirect_if_not_tls = src.redirect_if_not_tls;
shared_addr->timeout.read = src.timeout.read;
shared_addr->timeout.write = src.timeout.write;
size_t num_http1 = 0; size_t num_http1 = 0;
size_t num_http2 = 0; size_t num_http2 = 0;

View File

@ -180,6 +180,11 @@ struct SharedDownstreamAddr {
// true if this group requires that client connection must be TLS, // true if this group requires that client connection must be TLS,
// and the request must be redirected to https URI. // and the request must be redirected to https URI.
bool redirect_if_not_tls; bool redirect_if_not_tls;
// Timeouts for backend connection.
struct {
ev_tstamp read;
ev_tstamp write;
} timeout;
}; };
struct DownstreamAddrGroup { struct DownstreamAddrGroup {