Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/admin-guide/files/records.config.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
====================

Expand Down
6 changes: 6 additions & 0 deletions mgmt/RecordsConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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}
,

//############
//#
Expand Down
6 changes: 6 additions & 0 deletions proxy/http2/HTTP2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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}));
Expand Down
3 changes: 3 additions & 0 deletions proxy/http2/HTTP2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};
39 changes: 31 additions & 8 deletions proxy/http2/Http2ClientSession.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you check this behavior? I bit doubt calling flush() prevents adding a new block. Because write_reenable() doesn't do actual write.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. There's no guarantee, and "flush" may be not a good name in that sense. Like the big frame case explained on next line, write_buffer may have multiple blocks, but it's just an unfortunate case (it always has been), so it doesn't affect the sending process.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the write_reenable gets the writing underway, correct? So it does ensure that we don't buffer up all the writes until the End of Stream.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and there is also receive window on H2 layer. Basically, the total size of the buffered data will not be bigger than window size. Strictly speaking, it can be bigger if we send H2 frames that are not under flow control, but the sizes are vary small compare to DATA frame.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a wild guess, but we can achieve the same performance by just bumping the buffer size? (without these pending data tracking and flush functions)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be better than current, but I think it would call SSLWriteBuffer more frequently than the one with pending mechanism, because the buffered data would be sent regardless of the amount of the data. The pending mechanism ensure such small writes don't happen.

// 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)
{
Expand Down Expand Up @@ -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;

Expand Down
22 changes: 14 additions & 8 deletions proxy/http2/Http2ClientSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
};
Expand Down
4 changes: 3 additions & 1 deletion proxy/http2/Http2ConnectionState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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);

Expand Down