This is an automated email from the ASF dual-hosted git repository.
maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 693b3cb Close a H2 connection if its stream error rate is high
693b3cb is described below
commit 693b3cb465d2867379a80e882899aca8ea98f389
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Tue Feb 12 10:01:44 2019 +0900
Close a H2 connection if its stream error rate is high
This adds a new setting for H2:
proxy.config.http2.stream_error_rate_threshold
This adds a new metric for a number of session closes because of the
error rate
proxy.process.http2.session_die_high_error_rate
A conection starts graceful close when its stream error rate (error per
ssn) exceeds the configured threshold.
---
doc/admin-guide/files/records.config.en.rst | 7 +++
mgmt/RecordsConfig.cc | 2 +
proxy/http2/HTTP2.cc | 5 +++
proxy/http2/HTTP2.h | 2 +
proxy/http2/Http2ClientSession.cc | 70 +++++++++++++++++++----------
proxy/http2/Http2ClientSession.h | 16 ++++---
proxy/http2/Http2ConnectionState.cc | 6 ++-
proxy/http2/Http2ConnectionState.h | 26 ++++++++++-
proxy/http2/Http2Stream.cc | 2 +-
9 files changed, 104 insertions(+), 32 deletions(-)
diff --git a/doc/admin-guide/files/records.config.en.rst
b/doc/admin-guide/files/records.config.en.rst
index 7a3236c..01e6d11 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -3712,6 +3712,13 @@ HTTP/2 Configuration
HTTP/2 connection to avoid duplicate pushes on the same connection. If the
maximum number is reached, new entries are not remembered.
+.. ts:cv:: CONFIG proxy.config.http2.stream_error_rate_threshold FLOAT 0.1
+ :reloadable:
+
+ This is the maximum stream error rate |TS| allows on an HTTP/2 connection.
+ |TS| gracefully closes connections that have stream error rates above this
+ setting by sending GOAWAY frames.
+
Plug-in Configuration
=====================
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 8bd392f..3e93abf 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1331,6 +1331,8 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.http2.zombie_debug_timeout_in", RECD_INT, "0",
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.http2.stream_error_rate_threshold", RECD_FLOAT,
"0.1", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
//# Add LOCAL Records Here
{RECT_LOCAL, "proxy.local.incoming_ip_to_bind", RECD_STRING, nullptr,
RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index 55bd94b..082c7b9 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -62,6 +62,7 @@ static const char *const HTTP2_STAT_SESSION_DIE_ACTIVE_NAME
= "pro
static const char *const HTTP2_STAT_SESSION_DIE_INACTIVE_NAME =
"proxy.process.http2.session_die_inactive";
static const char *const HTTP2_STAT_SESSION_DIE_EOS_NAME =
"proxy.process.http2.session_die_eos";
static const char *const HTTP2_STAT_SESSION_DIE_ERROR_NAME =
"proxy.process.http2.session_die_error";
+static const char *const HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE_NAME =
"proxy.process.http2.session_die_high_error_rate";
union byte_pointer {
byte_pointer(void *p) : ptr(p) {}
@@ -731,6 +732,7 @@ uint32_t Http2::no_activity_timeout_in = 120;
uint32_t Http2::active_timeout_in = 0;
uint32_t Http2::push_diary_size = 256;
uint32_t Http2::zombie_timeout_in = 0;
+float Http2::stream_error_rate_threshold = 0.1;
void
Http2::init()
@@ -748,6 +750,7 @@ Http2::init()
REC_EstablishStaticConfigInt32U(active_timeout_in,
"proxy.config.http2.active_timeout_in");
REC_EstablishStaticConfigInt32U(push_diary_size,
"proxy.config.http2.push_diary_size");
REC_EstablishStaticConfigInt32U(zombie_timeout_in,
"proxy.config.http2.zombie_debug_timeout_in");
+ REC_EstablishStaticConfigFloat(stream_error_rate_threshold,
"proxy.config.http2.stream_error_rate_threshold");
// If any settings is broken, ATS should not start
ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
max_concurrent_streams_in}));
@@ -796,6 +799,8 @@ Http2::init()
static_cast<int>(HTTP2_STAT_SESSION_DIE_INACTIVE),
RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS,
HTTP2_STAT_SESSION_DIE_ERROR_NAME, RECD_INT, RECP_PERSISTENT,
static_cast<int>(HTTP2_STAT_SESSION_DIE_ERROR),
RecRawStatSyncSum);
+ RecRegisterRawStat(http2_rsb, RECT_PROCESS,
HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE_NAME, RECD_INT, RECP_PERSISTENT,
+ static_cast<int>(HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE),
RecRawStatSyncSum);
}
#if TS_HAS_TESTS
diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h
index b344743..9289b40 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -83,6 +83,7 @@ enum {
HTTP2_STAT_SESSION_DIE_INACTIVE,
HTTP2_STAT_SESSION_DIE_EOS,
HTTP2_STAT_SESSION_DIE_ERROR,
+ HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE,
HTTP2_N_STATS // Terminal counter, NOT A STAT INDEX.
};
@@ -377,6 +378,7 @@ public:
static uint32_t active_timeout_in;
static uint32_t push_diary_size;
static uint32_t zombie_timeout_in;
+ static float stream_error_rate_threshold;
static void init();
};
diff --git a/proxy/http2/Http2ClientSession.cc
b/proxy/http2/Http2ClientSession.cc
index 7774d01..dafa43e 100644
--- a/proxy/http2/Http2ClientSession.cc
+++ b/proxy/http2/Http2ClientSession.cc
@@ -103,25 +103,37 @@ Http2ClientSession::free()
// Update stats on how we died. May want to eliminate this. Was useful for
// tracking down which cases we were having problems cleaning up. But for
general
// use probably not worth the effort
- switch (dying_event) {
- case VC_EVENT_NONE:
- HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_DEFAULT,
this_ethread());
- break;
- case VC_EVENT_ACTIVE_TIMEOUT:
- HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ACTIVE,
this_ethread());
- break;
- case VC_EVENT_INACTIVITY_TIMEOUT:
- HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_INACTIVE,
this_ethread());
- break;
- case VC_EVENT_ERROR:
- HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ERROR,
this_ethread());
- break;
- case VC_EVENT_EOS:
- HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_EOS,
this_ethread());
- break;
- default:
- HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER,
this_ethread());
- break;
+ if (cause_of_death != Http2SessionCod::NOT_PROVIDED) {
+ switch (cause_of_death) {
+ case Http2SessionCod::HIGH_ERROR_RATE:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE,
this_ethread());
+ break;
+ case Http2SessionCod::NOT_PROVIDED:
+ // Can't happen but this case is here to not have default case.
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER,
this_ethread());
+ break;
+ }
+ } else {
+ switch (dying_event) {
+ case VC_EVENT_NONE:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_DEFAULT,
this_ethread());
+ break;
+ case VC_EVENT_ACTIVE_TIMEOUT:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ACTIVE,
this_ethread());
+ break;
+ case VC_EVENT_INACTIVITY_TIMEOUT:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_INACTIVE,
this_ethread());
+ break;
+ case VC_EVENT_ERROR:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_ERROR,
this_ethread());
+ break;
+ case VC_EVENT_EOS:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_EOS,
this_ethread());
+ break;
+ default:
+ HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_SESSION_DIE_OTHER,
this_ethread());
+ break;
+ }
}
ink_release_assert(this->client_vc == nullptr);
@@ -360,13 +372,25 @@ Http2ClientSession::main_event_handler(int event, void
*edata)
break;
}
- if (!this->is_draining()) {
+ if (!this->is_draining() && this->connection_state.get_shutdown_reason() ==
Http2ErrorCode::HTTP2_ERROR_MAX) {
this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NONE);
}
- // For a case we already checked Connection header and it didn't exist
- if (this->is_draining() && this->connection_state.get_shutdown_state() ==
HTTP2_SHUTDOWN_NONE) {
- this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED);
+ if (this->connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) {
+ if (this->is_draining()) { // For a case we already checked Connection
header and it didn't exist
+ Http2SsnDebug("Preparing for graceful shutdown because of draining
state");
+ this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED);
+ } else if (this->connection_state.get_stream_error_rate() >
+ Http2::stream_error_rate_threshold) { // For a case many stream
errors happened
+ ip_port_text_buffer ipb;
+ const char *client_ip = ats_ip_ntop(get_client_addr(), ipb, sizeof(ipb));
+ Error("HTTP/2 session error client_ip=%s session_id=%" PRId64
+ " closing a connection, because its stream error rate (%f)
exceeded the threshold (%f)",
+ client_ip, connection_id(),
this->connection_state.get_stream_error_rate(),
Http2::stream_error_rate_threshold);
+ Http2SsnDebug("Preparing for graceful shutdown because of a high stream
error rate");
+ cause_of_death = Http2SessionCod::HIGH_ERROR_RATE;
+ this->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED,
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM);
+ }
}
if (this->connection_state.get_shutdown_state() ==
HTTP2_SHUTDOWN_NOT_INITIATED) {
diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h
index 9a2c5a1..06e6262 100644
--- a/proxy/http2/Http2ClientSession.h
+++ b/proxy/http2/Http2ClientSession.h
@@ -44,6 +44,11 @@
#define HTTP2_SESSION_EVENT_SHUTDOWN_INIT (HTTP2_SESSION_EVENTS_START + 5)
#define HTTP2_SESSION_EVENT_SHUTDOWN_CONT (HTTP2_SESSION_EVENTS_START + 6)
+enum class Http2SessionCod : int {
+ NOT_PROVIDED,
+ HIGH_ERROR_RATE,
+};
+
size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX =
CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
// To support Upgrade: h2c
@@ -342,11 +347,12 @@ private:
// For Upgrade: h2c
Http2UpgradeContext upgrade_context;
- VIO *write_vio = nullptr;
- int dying_event = 0;
- bool kill_me = false;
- bool half_close_local = false;
- int recursion = 0;
+ VIO *write_vio = nullptr;
+ int dying_event = 0;
+ bool kill_me = false;
+ Http2SessionCod cause_of_death = Http2SessionCod::NOT_PROVIDED;
+ bool half_close_local = false;
+ int recursion = 0;
std::unordered_set<std::string> h2_pushed_urls;
};
diff --git a/proxy/http2/Http2ConnectionState.cc
b/proxy/http2/Http2ConnectionState.cc
index 3da2dd7..9db9caf 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -996,7 +996,10 @@ Http2ConnectionState::main_event_handler(int event, void
*edata)
shutdown_state = HTTP2_SHUTDOWN_IN_PROGRESS;
// [RFC 7540] 6.8. GOAWAY
// ..., the server can send another GOAWAY frame with an updated last
stream identifier
- send_goaway_frame(latest_streamid_in,
Http2ErrorCode::HTTP2_ERROR_NO_ERROR);
+ if (shutdown_reason == Http2ErrorCode::HTTP2_ERROR_MAX) {
+ shutdown_reason = Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+ }
+ send_goaway_frame(latest_streamid_in, shutdown_reason);
// Stop creating new streams
SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
this->ua_session->set_half_close_local_flag(true);
@@ -1693,6 +1696,7 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId
id, Http2ErrorCode ec)
if (ec != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_STREAM_ERRORS_COUNT,
this_ethread());
+ ++stream_error_count;
}
Http2Frame rst_stream(HTTP2_FRAME_TYPE_RST_STREAM, id, 0);
diff --git a/proxy/http2/Http2ConnectionState.h
b/proxy/http2/Http2ConnectionState.h
index c2eea39..50c2b39 100644
--- a/proxy/http2/Http2ConnectionState.h
+++ b/proxy/http2/Http2ConnectionState.h
@@ -221,6 +221,23 @@ public:
return client_streams_in_count;
}
+ double
+ get_stream_error_rate() const
+ {
+ int total = get_stream_requests();
+ if (total > 0) {
+ return (double)stream_error_count / (double)total;
+ } else {
+ return 0;
+ }
+ }
+
+ Http2ErrorCode
+ get_shutdown_reason() const
+ {
+ return shutdown_reason;
+ }
+
// Connection level window size
ssize_t client_rwnd = HTTP2_INITIAL_WINDOW_SIZE;
ssize_t server_rwnd = Http2::initial_window_size;
@@ -267,9 +284,10 @@ public:
}
void
- set_shutdown_state(Http2ShutdownState state)
+ set_shutdown_state(Http2ShutdownState state, Http2ErrorCode reason =
Http2ErrorCode::HTTP2_ERROR_NO_ERROR)
{
- shutdown_state = state;
+ shutdown_state = state;
+ shutdown_reason = reason;
}
// noncopyable
@@ -316,6 +334,9 @@ private:
// Counter for current active streams and streams in the process of shutting
down
std::atomic<uint32_t> total_client_streams_count = 0;
+ // Counter for stream errors ATS sent
+ uint32_t stream_error_count = 0;
+
// NOTE: Id of stream which MUST receive CONTINUATION frame.
// - [RFC 7540] 6.2 HEADERS
// "A HEADERS frame without the END_HEADERS flag set MUST be followed by
a
@@ -328,6 +349,7 @@ private:
bool fini_received = false;
int recursion = 0;
Http2ShutdownState shutdown_state = HTTP2_SHUTDOWN_NONE;
+ Http2ErrorCode shutdown_reason = Http2ErrorCode::HTTP2_ERROR_MAX;
Event *shutdown_cont_event = nullptr;
Event *fini_event = nullptr;
Event *zombie_event = nullptr;
diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc
index fc6e3b4..83da4e1 100644
--- a/proxy/http2/Http2Stream.cc
+++ b/proxy/http2/Http2Stream.cc
@@ -603,7 +603,7 @@ Http2Stream::update_write_request(IOBufferReader
*buf_reader, int64_t write_len,
if (memcmp(HTTP_VALUE_CLOSE, value, HTTP_LEN_CLOSE) == 0) {
SCOPED_MUTEX_LOCK(lock, parent->connection_state.mutex,
this_ethread());
if (parent->connection_state.get_shutdown_state() ==
HTTP2_SHUTDOWN_NONE) {
-
parent->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED);
+
parent->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED,
Http2ErrorCode::HTTP2_ERROR_NO_ERROR);
}
}
}