This is an automated email from the ASF dual-hosted git repository. masaori 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 b46f5d061a Add http2.incomplete_header_timeout_in (#11354) b46f5d061a is described below commit b46f5d061af455dc75956346da3308788381b30d Author: Masaori Koshiba <masa...@apache.org> AuthorDate: Fri May 24 08:00:11 2024 +0900 Add http2.incomplete_header_timeout_in (#11354) --- doc/admin-guide/files/records.yaml.en.rst | 7 +++++++ include/proxy/http2/HTTP2.h | 1 + include/proxy/http2/Http2CommonSession.h | 1 + src/proxy/http2/HTTP2.cc | 33 +++++++++++++++++-------------- src/proxy/http2/Http2ConnectionState.cc | 24 ++++++++++++++++++++++ src/proxy/http2/Http2Stream.cc | 29 ++++++++++++++++++++++++++- src/records/RecordsConfig.cc | 2 ++ 7 files changed, 81 insertions(+), 16 deletions(-) diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst index ba94d190b7..00fde245e1 100644 --- a/doc/admin-guide/files/records.yaml.en.rst +++ b/doc/admin-guide/files/records.yaml.en.rst @@ -4500,6 +4500,13 @@ HTTP/2 Configuration Specifies how long |TS| keeps connections to origins open if a transaction stalls. +.. ts:cv:: CONFIG proxy.config.http2.incomplete_header_timeout_in INT 10 + :reloadable: + :units: seconds + + Specifies how long |TS| keeps streams to clients open after they start sending HTTP headers. If a client doesn't send all + headers within this time, the stream and connection will be closed. + .. ts:cv:: CONFIG proxy.config.http2.zombie_debug_timeout_in INT 0 :reloadable: diff --git a/include/proxy/http2/HTTP2.h b/include/proxy/http2/HTTP2.h index 5cabf2b2cd..11c60ec35f 100644 --- a/include/proxy/http2/HTTP2.h +++ b/include/proxy/http2/HTTP2.h @@ -410,6 +410,7 @@ public: static uint32_t accept_no_activity_timeout; static uint32_t no_activity_timeout_in; static uint32_t active_timeout_in; + static uint32_t incomplete_header_timeout_in; static uint32_t push_diary_size; static uint32_t zombie_timeout_in; diff --git a/include/proxy/http2/Http2CommonSession.h b/include/proxy/http2/Http2CommonSession.h index 5507a3a07f..cb61107fe9 100644 --- a/include/proxy/http2/Http2CommonSession.h +++ b/include/proxy/http2/Http2CommonSession.h @@ -47,6 +47,7 @@ #define HTTP2_SESSION_EVENT_SHUTDOWN_INIT (HTTP2_SESSION_EVENTS_START + 7) #define HTTP2_SESSION_EVENT_SHUTDOWN_CONT (HTTP2_SESSION_EVENTS_START + 8) #define HTTP2_SESSION_EVENT_REENABLE (HTTP2_SESSION_EVENTS_START + 9) +#define HTTP2_SESSION_EVENT_ERROR (HTTP2_SESSION_EVENTS_START + 10) enum class Http2SessionCod : int { NOT_PROVIDED, diff --git a/src/proxy/http2/HTTP2.cc b/src/proxy/http2/HTTP2.cc index 3de6bbab04..646da22050 100644 --- a/src/proxy/http2/HTTP2.cc +++ b/src/proxy/http2/HTTP2.cc @@ -459,21 +459,23 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_ } // Initialize this subsystem with librecords configs (for now) -uint32_t Http2::max_concurrent_streams_in = 100; -uint32_t Http2::min_concurrent_streams_in = 10; -uint32_t Http2::max_active_streams_in = 0; -bool Http2::throttling = false; -uint32_t Http2::stream_priority_enabled = 0; -uint32_t Http2::initial_window_size_in = 65535; -Http2FlowControlPolicy Http2::flow_control_policy_in = Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM; -uint32_t Http2::max_frame_size = 16384; -uint32_t Http2::header_table_size = 4096; -uint32_t Http2::max_header_list_size = 4294967295; -uint32_t Http2::accept_no_activity_timeout = 120; -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; +uint32_t Http2::max_concurrent_streams_in = 100; +uint32_t Http2::min_concurrent_streams_in = 10; +uint32_t Http2::max_active_streams_in = 0; +bool Http2::throttling = false; +uint32_t Http2::stream_priority_enabled = 0; +uint32_t Http2::initial_window_size_in = 65535; +Http2FlowControlPolicy Http2::flow_control_policy_in = Http2FlowControlPolicy::STATIC_SESSION_AND_STATIC_STREAM; +uint32_t Http2::max_frame_size = 16384; +uint32_t Http2::header_table_size = 4096; +uint32_t Http2::max_header_list_size = 4294967295; + +uint32_t Http2::accept_no_activity_timeout = 120; +uint32_t Http2::no_activity_timeout_in = 120; +uint32_t Http2::active_timeout_in = 0; +uint32_t Http2::incomplete_header_timeout_in = 10; +uint32_t Http2::push_diary_size = 256; +uint32_t Http2::zombie_timeout_in = 0; uint32_t Http2::max_concurrent_streams_out = 100; uint32_t Http2::min_concurrent_streams_out = 10; @@ -537,6 +539,7 @@ Http2::init() REC_EstablishStaticConfigInt32U(no_activity_timeout_in, "proxy.config.http2.no_activity_timeout_in"); REC_EstablishStaticConfigInt32U(no_activity_timeout_out, "proxy.config.http2.no_activity_timeout_out"); REC_EstablishStaticConfigInt32U(active_timeout_in, "proxy.config.http2.active_timeout_in"); + REC_EstablishStaticConfigInt32U(incomplete_header_timeout_in, "proxy.config.http2.incomplete_header_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"); diff --git a/src/proxy/http2/Http2ConnectionState.cc b/src/proxy/http2/Http2ConnectionState.cc index 4baaa52b52..ae739e9c3a 100644 --- a/src/proxy/http2/Http2ConnectionState.cc +++ b/src/proxy/http2/Http2ConnectionState.cc @@ -36,6 +36,7 @@ #include "iocore/net/TLSSNISupport.h" #include "tscore/ink_assert.h" +#include "tscore/ink_hrtime.h" #include "tscore/ink_memory.h" #include "tsutil/PostScript.h" #include "tsutil/LocalBuffer.h" @@ -493,6 +494,7 @@ Http2ConnectionState::rcv_headers_frame(const Http2Frame &frame) if (!stream->is_outbound_connection() && !stream->trailing_header_is_possible()) { SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread()); stream->mark_milestone(Http2StreamMilestone::START_TXN); + stream->cancel_active_timeout(); stream->new_transaction(frame.is_from_early_data()); // Send request header to SM stream->send_headers(*this); @@ -1518,6 +1520,23 @@ Http2ConnectionState::main_event_handler(int event, void *edata) this->session->flush(); } break; + case HTTP2_SESSION_EVENT_ERROR: { + REMEMBER(event, this->recursion); + + Http2ErrorCode error_code = Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR; + if (edata != nullptr) { + Http2Error *error = static_cast<Http2Error *>(edata); + error_code = error->code; + } + + SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); + this->send_goaway_frame(this->latest_streamid_in, error_code); + this->session->set_half_close_local_flag(true); + if (fini_event == nullptr) { + this->fini_event = this_ethread()->schedule_imm_local(static_cast<Continuation *>(this), HTTP2_SESSION_EVENT_FINI); + } + } break; + // Initiate a graceful shutdown case HTTP2_SESSION_EVENT_SHUTDOWN_INIT: { REMEMBER(event, this->recursion); @@ -1820,6 +1839,11 @@ Http2ConnectionState::create_stream(Http2StreamId new_id, Http2Error &error) } increment_stream_requests(); + // Set incomplete header timeout + // Client should send END_HEADERS flag within the http2.incomplete_header_timeout_in. + // The active timeout of this stream will be reset by HttpSM with http.transction_active_timeout_in when a HTTP TXN is started + new_stream->set_active_timeout(HRTIME_SECONDS(Http2::incomplete_header_timeout_in)); + // Clear the session timeout. Let the transaction timeouts reign session->get_proxy_session()->cancel_inactivity_timeout(); session->get_proxy_session()->set_session_active(); diff --git a/src/proxy/http2/Http2Stream.cc b/src/proxy/http2/Http2Stream.cc index 2540b7b2b2..44d50e703b 100644 --- a/src/proxy/http2/Http2Stream.cc +++ b/src/proxy/http2/Http2Stream.cc @@ -28,6 +28,7 @@ #include "proxy/http2/Http2ServerSession.h" #include "proxy/http/HttpDebugNames.h" #include "proxy/http/HttpSM.h" +#include "tscore/Diags.h" #include "tscore/HTTPVersion.h" #include "tscore/ink_assert.h" @@ -196,10 +197,36 @@ Http2Stream::main_event_handler(int event, void *edata) switch (event) { case VC_EVENT_ACTIVE_TIMEOUT: case VC_EVENT_INACTIVITY_TIMEOUT: - if (_sm && read_vio.ntodo() > 0) { + if (_sm == nullptr && closed != true) { + // TIMEOUT without HttpSM - assuming incomplete header timeout + Http2StreamDebug("timeout event=%d", event); + + ip_port_text_buffer ipb; + const char *remote_ip = ats_ip_ntop(this->_proxy_ssn->get_remote_addr(), ipb, sizeof(ipb)); + + Error("HTTP/2 stream error timeout remote_ip=%s session_id=%" PRId64 " stream_id=%u event=%d", remote_ip, + this->_proxy_ssn->connection_id(), this->_id, event); + + // Close stream + do_io_close(); + terminate_stream = true; + + // Close connection because this stream doesn't read HEADERS/CONTINUATION frames anymore that makes HPACK dynamic table + // out of sync. + Http2ConnectionState &connection_state = get_connection_state(); + { + SCOPED_MUTEX_LOCK(lock, connection_state.mutex, this_ethread()); + Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR, + "stream timeout"); + connection_state.handleEvent(HTTP2_SESSION_EVENT_ERROR, &error); + } + } else if (_sm && read_vio.ntodo() > 0) { this->signal_read_event(event); } else if (_sm && write_vio.ntodo() > 0) { this->signal_write_event(event); + } else { + Warning("HTTP/2 unknown case of %d event - session_id=%" PRId64 " stream_id=%u", event, this->_proxy_ssn->connection_id(), + this->_id); } break; case VC_EVENT_WRITE_READY: diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc index d80af3aff1..4673cdebc6 100644 --- a/src/records/RecordsConfig.cc +++ b/src/records/RecordsConfig.cc @@ -1294,6 +1294,8 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http2.active_timeout_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , + {RECT_CONFIG, "proxy.config.http2.incomplete_header_timeout_in", RECD_INT, "10", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , {RECT_CONFIG, "proxy.config.http2.push_diary_size", RECD_INT, "256", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , {RECT_CONFIG, "proxy.config.http2.zombie_debug_timeout_in", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}