diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index 7e4a97e5bce..e82055095dd 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -3901,6 +3901,27 @@ HTTP/2 Configuration Clients that send smaller window increments lower than this limit will be immediately disconnected with an error code of ENHANCE_YOUR_CALM. +.. ts:cv:: CONFIG proxy.config.http2.write_buffer_block_size INT 262144 + :reloadable: + + Specifies the size of a buffer block that is used for buffering outgoing + HTTP/2 frames. The size will be rounded up based on power of 2. + +.. ts:cv:: CONFIG proxy.config.http2.write_size_threshold FLOAT 0.5 + :reloadable: + + Specifies the size threshold for triggering write operation for sending HTTP/2 + frames. The default value is 0.5 and it measn write operation is going to be + triggered when half or more of the buffer is occupied. + +.. ts:cv:: CONFIG proxy.config.http2.write_time_threshold INT 100 + :reloadable: + :units: milliseconds + + Specifies the time threshold for triggering write operation for sending HTTP/2 + frames. Write operation will be triggered at least once every this configured + number of millisecond regardless of pending data size. + HTTP/3 Configuration ==================== diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index 30ecbcc8dff..41a98e3cc05 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -1333,6 +1333,12 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http2.header_table_size_limit", RECD_INT, "65536", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , + {RECT_CONFIG, "proxy.config.http2.write_buffer_block_size", RECD_INT, "262144", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http2.write_size_threshold", RECD_FLOAT, "0.5", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.http2.write_time_threshold", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} + , //############ //# diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 4254dd48dc3..50a5d236440 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -805,6 +805,9 @@ float Http2::min_avg_window_update = 2560.0; uint32_t Http2::con_slow_log_threshold = 0; uint32_t Http2::stream_slow_log_threshold = 0; uint32_t Http2::header_table_size_limit = 65536; +uint32_t Http2::write_buffer_block_size = 262144; +float Http2::write_size_threshold = 0.5; +uint32_t Http2::write_time_threshold = 100; void Http2::init() @@ -832,6 +835,9 @@ Http2::init() REC_EstablishStaticConfigInt32U(con_slow_log_threshold, "proxy.config.http2.connection.slow.log.threshold"); REC_EstablishStaticConfigInt32U(stream_slow_log_threshold, "proxy.config.http2.stream.slow.log.threshold"); REC_EstablishStaticConfigInt32U(header_table_size_limit, "proxy.config.http2.header_table_size_limit"); + REC_EstablishStaticConfigInt32U(write_buffer_block_size, "proxy.config.http2.write_buffer_block_size"); + REC_EstablishStaticConfigFloat(write_size_threshold, "proxy.config.http2.write_size_threshold"); + REC_EstablishStaticConfigInt32U(write_time_threshold, "proxy.config.http2.write_time_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})); diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index 966164508a5..d2eed22ffe3 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -403,6 +403,9 @@ class Http2 static uint32_t con_slow_log_threshold; static uint32_t stream_slow_log_threshold; static uint32_t header_table_size_limit; + static uint32_t write_buffer_block_size; + static float write_size_threshold; + static uint32_t write_time_threshold; static void init(); }; diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index fb2650ce757..bba7611264f 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -217,9 +217,11 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB this->read_buffer->water_mark = connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE); this->_reader = reader ? reader : this->read_buffer->alloc_reader(); - // Set write buffer size to max size of TLS record (16KB) - this->write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_16K); - this->sm_writer = this->write_buffer->alloc_reader(); + // This block size is the buffer size that we pass to SSLWriteBuffer + auto buffer_block_size_index = iobuffer_size_to_index(Http2::write_buffer_block_size, MAX_BUFFER_SIZE_INDEX); + this->write_buffer = new_MIOBuffer(buffer_block_size_index); + this->sm_writer = this->write_buffer->alloc_reader(); + this->_write_size_threshold = index_to_buffer_size(buffer_block_size_index) * Http2::write_size_threshold; this->_handle_if_ssl(new_vc); @@ -299,18 +301,37 @@ Http2ClientSession::set_half_close_local_flag(bool flag) } int64_t -Http2ClientSession::xmit(const Http2TxFrame &frame) +Http2ClientSession::xmit(const Http2TxFrame &frame, bool flush) { int64_t len = frame.write_to(this->write_buffer); + this->_pending_sending_data_size += len; + // Force flush for some cases + if (!flush) { + // Flush if we already use half of the buffer to avoid adding a new block to the chain. + // A frame size can be 16MB at maximum so blocks can be added, but that's fine. + if (this->_pending_sending_data_size >= this->_write_size_threshold) { + flush = true; + } + } - if (len > 0) { - total_write_len += len; - write_reenable(); + if (flush) { + this->flush(); } return len; } +void +Http2ClientSession::flush() +{ + if (this->_pending_sending_data_size > 0) { + total_write_len += this->_pending_sending_data_size; + this->_pending_sending_data_size = 0; + this->_write_buffer_last_flush = Thread::get_hrtime(); + write_reenable(); + } +} + int Http2ClientSession::main_event_handler(int event, void *edata) { @@ -355,7 +376,9 @@ Http2ClientSession::main_event_handler(int event, void *edata) case VC_EVENT_WRITE_READY: case VC_EVENT_WRITE_COMPLETE: this->connection_state.restart_streams(); - + if ((Thread::get_hrtime() >= this->_write_buffer_last_flush + HRTIME_MSECONDS(this->_write_time_threshold))) { + this->flush(); + } retval = 0; break; diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h index 0f1f64bb1cc..c7ba07f2a67 100644 --- a/proxy/http2/Http2ClientSession.h +++ b/proxy/http2/Http2ClientSession.h @@ -101,7 +101,8 @@ class Http2ClientSession : public ProxySession // more methods void write_reenable(); - int64_t xmit(const Http2TxFrame &frame); + int64_t xmit(const Http2TxFrame &frame, bool flush = true); + void flush(); //////////////////// // Accessors @@ -154,13 +155,16 @@ class Http2ClientSession : public ProxySession bool _should_do_something_else(); - int64_t total_write_len = 0; - SessionHandler session_handler = nullptr; - MIOBuffer *read_buffer = nullptr; - IOBufferReader *_reader = nullptr; - MIOBuffer *write_buffer = nullptr; - IOBufferReader *sm_writer = nullptr; - Http2FrameHeader current_hdr = {0, 0, 0, 0}; + int64_t total_write_len = 0; + SessionHandler session_handler = nullptr; + MIOBuffer *read_buffer = nullptr; + IOBufferReader *_reader = nullptr; + MIOBuffer *write_buffer = nullptr; + IOBufferReader *sm_writer = nullptr; + Http2FrameHeader current_hdr = {0, 0, 0, 0}; + uint32_t _write_size_threshold = 0; + uint32_t _write_time_threshold = 100; + ink_hrtime _write_buffer_last_flush = 0; IpEndpoint cached_client_addr; IpEndpoint cached_local_addr; @@ -183,6 +187,8 @@ class Http2ClientSession : public ProxySession Event *_reenable_event = nullptr; int _n_frame_read = 0; + uint32_t _pending_sending_data_size = 0; + int64_t read_from_early_data = 0; bool cur_frame_from_early_data = false; }; diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index 1f57a881d05..b2597e80923 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -1547,6 +1547,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len // We only need to check for window size when there is a payload if (window_size <= 0) { Http2StreamDebug(this->ua_session, stream->get_id(), "No window"); + this->ua_session->flush(); return Http2SendDataFrameResult::NO_WINDOW; } @@ -1564,6 +1565,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len // OK if there is no body yet. Otherwise continue on to send a DATA frame and delete the stream if (!stream->is_write_vio_done() && payload_length == 0) { Http2StreamDebug(this->ua_session, stream->get_id(), "No payload"); + this->ua_session->flush(); return Http2SendDataFrameResult::NO_PAYLOAD; } @@ -1580,7 +1582,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len _client_rwnd, stream->client_rwnd(), payload_length); Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length); - this->ua_session->xmit(data); + this->ua_session->xmit(data, flags & HTTP2_FLAGS_DATA_END_STREAM); stream->update_sent_count(payload_length);