Skip to content
Open
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
4 changes: 4 additions & 0 deletions include/iocore/net/qmux/QMuxConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,8 @@ class QMuxConnection : public QUICConnection, public Continuation, public QUICSt
MIOBuffer *_write_buf = nullptr;
VIO *_write_vio = nullptr;
Event *_quiche_timeout = nullptr;

// Writable-stream count from the previous _handle_write_streams() call, used to
// size this event's per-stream send budget (see QUICStream::compute_fair_send_budget()).
size_t _last_writable_stream_count = 1;
};
15 changes: 14 additions & 1 deletion include/iocore/net/quic/QUICStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ class QUICStream
public:
using ErrorCode = uint64_t; //!< recv/send stream application error codes.

// Guaranteed per-stream send budget for one write event when many streams are
// contending for the connection's write path this round.
static constexpr size_t MIN_STREAM_SEND_BYTES_PER_EVENT = 16 * 1024;
// Ceiling on how much a single stream can send in one write event; only reached
// when few streams are contending, per compute_fair_send_budget().
static constexpr size_t MAX_STREAM_SEND_BYTES_PER_EVENT = 256 * 1024;

QUICStream() {}
QUICStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid);
virtual ~QUICStream();
Expand All @@ -76,7 +83,13 @@ class QUICStream
void reset(QUICStreamErrorUPtr error);

void receive_data(QUICStreamIO &stream_io);
int64_t send_data(QUICStreamIO &stream_io);
int64_t send_data(QUICStreamIO &stream_io, size_t max_bytes_this_event);

// Computes the per-stream send budget for one write event given how many streams
// were writable in the previous event on this connection. Scales down toward
// MIN_STREAM_SEND_BYTES_PER_EVENT under contention, up toward
// MAX_STREAM_SEND_BYTES_PER_EVENT when a stream has the write path to itself.
static size_t compute_fair_send_budget(size_t num_writable_streams);

/*
* QUICApplication need to call one of these functions when it process VC_EVENT_*
Expand Down
26 changes: 8 additions & 18 deletions include/proxy/http3/Http3Frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,6 @@ class Http3Frame
bool _is_ready = false;
};

class Http3UnknownFrame : public Http3Frame
{
public:
Http3UnknownFrame() : Http3Frame() {}
Http3UnknownFrame(IOBufferReader &reader);

Ptr<IOBufferBlock> to_io_buffer_block() const override;

protected:
const uint8_t *_buf = nullptr;
size_t _buf_len = 0;
};

//
// DATA Frame
//
Expand Down Expand Up @@ -112,7 +99,11 @@ class Http3HeadersFrame : public Http3Frame
public:
Http3HeadersFrame() : Http3Frame() {}
Http3HeadersFrame(IOBufferReader &reader);
Http3HeadersFrame(ats_unique_buf header_block, size_t header_block_len);
// Shares the caller's buffer via a cloned reader instead of copying header_block_len bytes.
// Safe as long as the source MIOBuffer outlives this frame, which holds for the qmux/quic
// write path: the frame is created, serialized via to_io_buffer_block(), and destroyed, all
// synchronously, well within the lifetime of the Http3HeaderFramer that owns the source buffer.
Http3HeadersFrame(IOBufferReader &header_block_reader, size_t header_block_len);
~Http3HeadersFrame();

Ptr<IOBufferBlock> to_io_buffer_block() const override;
Expand All @@ -125,9 +116,9 @@ class Http3HeadersFrame : public Http3Frame
bool _parse() override;

private:
uint8_t *_header_block = nullptr;
ats_unique_buf _header_block_uptr = {nullptr};
size_t _header_block_len = 0;
uint8_t *_header_block = nullptr;
size_t _header_block_len = 0;
IOBufferReader *_header_block_reader = nullptr;
};

//
Expand Down Expand Up @@ -247,7 +238,6 @@ class Http3FrameFactory
/*
* Creates a HEADERS frame.
*/
static Http3HeadersFrameUPtr create_headers_frame(const uint8_t *header_block, size_t header_block_len);
static Http3HeadersFrameUPtr create_headers_frame(IOBufferReader *header_block_reader, size_t header_block_len);

/*
Expand Down
4 changes: 4 additions & 0 deletions include/proxy/http3/Http3FrameCollector.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class QUICStreamVCAdapter;
class Http3FrameCollector
{
public:
// Http3Transaction always adds exactly 2 generators (header framer, data framer) per
// transaction; reserving avoids the growth-triggered reallocation on the second add_generator().
Http3FrameCollector() { _generators.reserve(2); }

Http3ErrorUPtr on_write_ready(QUICStreamId stream_id, MIOBuffer &writer, size_t &nread, bool &all_done);

void add_generator(Http3FrameGenerator *generator);
Expand Down
2 changes: 1 addition & 1 deletion include/proxy/http3/Http3FrameCounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Http3FrameCounter : public Http3FrameHandler
Http3FrameCounter(){};

// Http3FrameHandler
std::vector<Http3FrameType> interests() override;
std::vector<Http3FrameType> const &interests() override;
Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame, Http3StreamType s_type = Http3StreamType::UNKNOWN) override;

uint64_t get_count(uint64_t type) const;
Expand Down
25 changes: 17 additions & 8 deletions include/proxy/http3/Http3FrameDispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
#include "iocore/net/quic/QUICApplication.h"
#include "proxy/http3/Http3Frame.h"
#include "proxy/http3/Http3FrameHandler.h"
#include <vector>
#include <array>
#include <cstdint>

class QUICStreamVCAdapter;

Expand All @@ -38,17 +39,25 @@ class Http3FrameDispatcher
void add_handler(Http3FrameHandler *handler);

private:
// At most a handful of handlers ever register interest in the same frame type (currently
// up to 3: the frame counter, the protocol enforcer, and one of the header/data handlers).
// Inline storage avoids a heap allocation per handler registration, which otherwise runs
// once per HTTP/3 request since this dispatcher is a per-transaction object.
static constexpr size_t MAX_HANDLERS_PER_TYPE = 4;

enum READING_STATE {
READING_TYPE_LEN,
READING_LENGTH_LEN,
READING_PAYLOAD_LEN,
READING_PAYLOAD,
} _reading_state = READING_TYPE_LEN;
int64_t _reading_frame_type_len;
int64_t _reading_frame_length_len;
uint64_t _reading_frame_payload_len;
uint64_t _bytes_to_skip;
Http3FrameFactory _frame_factory;
std::shared_ptr<Http3Frame> _current_frame = nullptr;
std::vector<Http3FrameHandler *> _handlers[256];
int64_t _reading_frame_type_len;
int64_t _reading_frame_length_len;
uint64_t _reading_frame_payload_len;
uint64_t _bytes_to_skip;
Http3FrameFactory _frame_factory;
std::shared_ptr<Http3Frame> _current_frame = nullptr;

std::array<Http3FrameHandler *, MAX_HANDLERS_PER_TYPE> _handlers[256] = {};
uint8_t _handler_count[256] = {};
};
6 changes: 3 additions & 3 deletions include/proxy/http3/Http3FrameHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Http3FrameHandler
{
public:
virtual ~Http3FrameHandler(){};
virtual std::vector<Http3FrameType> interests() = 0;
virtual Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame,
Http3StreamType s_type = Http3StreamType::UNKNOWN) = 0;
virtual std::vector<Http3FrameType> const &interests() = 0;
virtual Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame,
Http3StreamType s_type = Http3StreamType::UNKNOWN) = 0;
};
2 changes: 1 addition & 1 deletion include/proxy/http3/Http3HeaderVIOAdaptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Http3HeaderVIOAdaptor : public Continuation, public Http3FrameHandler
~Http3HeaderVIOAdaptor();

// Http3FrameHandler
std::vector<Http3FrameType> interests() override;
std::vector<Http3FrameType> const &interests() override;
Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame, Http3StreamType s_type = Http3StreamType::UNKNOWN) override;

bool is_complete();
Expand Down
2 changes: 1 addition & 1 deletion include/proxy/http3/Http3ProtocolEnforcer.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Http3ProtocolEnforcer : public Http3FrameHandler
Http3ProtocolEnforcer(){};

// Http3FrameHandler
std::vector<Http3FrameType> interests() override;
std::vector<Http3FrameType> const &interests() override;
Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame, Http3StreamType s_type = Http3StreamType::UNKNOWN) override;

private:
Expand Down
2 changes: 1 addition & 1 deletion include/proxy/http3/Http3SettingsHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Http3SettingsHandler : public Http3FrameHandler
Http3SettingsHandler(Http3Session *session) : _session(session){};

// Http3FrameHandler
std::vector<Http3FrameType> interests() override;
std::vector<Http3FrameType> const &interests() override;
Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame, Http3StreamType s_type = Http3StreamType::UNKNOWN) override;

private:
Expand Down
4 changes: 2 additions & 2 deletions include/proxy/http3/Http3StreamDataVIOAdaptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ class Http3StreamDataVIOAdaptor : public Http3FrameHandler
virtual ~Http3StreamDataVIOAdaptor();

// Http3FrameHandler
std::vector<Http3FrameType> interests() override;
std::vector<Http3FrameType> const &interests() override;
Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame, Http3StreamType s_type = Http3StreamType::UNKNOWN) override;

// Http3StreamDataVIOAdaptor
void finalize();
bool has_data();
bool has_data() const;

private:
VIO *_sink_vio = nullptr;
Expand Down
24 changes: 12 additions & 12 deletions include/proxy/http3/Http3Transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@
#include "iocore/net/quic/QUICStreamVCAdapter.h"
#include "proxy/http3/Http3FrameDispatcher.h"
#include "proxy/http3/Http3FrameCollector.h"
#include "proxy/http3/Http3HeaderFramer.h"
#include "proxy/http3/Http3DataFramer.h"
#include "proxy/http3/Http3ProtocolEnforcer.h"
#include "proxy/http3/Http3HeaderVIOAdaptor.h"
#include "proxy/http3/Http3StreamDataVIOAdaptor.h"

#include <functional>

class QUICStreamIO;
class HQSession;
class Http09Session;
class Http3Session;
class Http3HeaderFramer;
class Http3DataFramer;
class Http3HeaderVIOAdaptor;
class Http3ProtocolEnforcer;
class Http3StreamDataVIOAdaptor;

class HQTransaction : public ProxyTransaction
{
Expand Down Expand Up @@ -151,13 +151,13 @@ class Http3Transaction : public HQTransaction
void _handle_error(const Http3Error &error);

// These are for HTTP/3
Http3FrameDispatcher _frame_dispatcher;
Http3FrameCollector _frame_collector;
Http3ProtocolEnforcer *_protocol_enforcer = nullptr;
Http3HeaderFramer *_header_framer = nullptr;
Http3DataFramer *_data_framer = nullptr;
Http3HeaderVIOAdaptor *_header_handler = nullptr;
Http3StreamDataVIOAdaptor *_data_handler = nullptr;
Http3FrameDispatcher _frame_dispatcher;
Http3FrameCollector _frame_collector;
Http3ProtocolEnforcer _protocol_enforcer;
Http3HeaderFramer _header_framer;
Http3DataFramer _data_framer;
Http3HeaderVIOAdaptor _header_handler;
Http3StreamDataVIOAdaptor _data_handler;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions src/iocore/net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ if(BUILD_TESTING)
if(TS_USE_QUIC)
target_sources(test_net PRIVATE unit_tests/test_QUICTokenKeyConfig.cc)
endif()
if(TS_USE_QUIC OR TS_USE_QMUX)
target_sources(test_net PRIVATE unit_tests/test_QUICStream.cc)
endif()
# Use link groups to solve circular dependency
set(LINK_GROUP_LIBS
ts::logging
Expand Down
4 changes: 2 additions & 2 deletions src/iocore/net/OpenSSLQUICNetVConnection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -749,9 +749,9 @@ QUICNetVConnection::_process_openssl_streams()
}
if ((stream_type & SSL_STREAM_TYPE_WRITE) != 0 || stream->has_data_to_send()) {
if (stream->has_data_to_send()) {
while (stream->has_data_to_send() && stream->send_data(*this) > 0) {}
while (stream->has_data_to_send() && stream->send_data(*this, QUICStream::MIN_STREAM_SEND_BYTES_PER_EVENT) > 0) {}
} else {
stream->send_data(*this);
stream->send_data(*this, QUICStream::MIN_STREAM_SEND_BYTES_PER_EVENT);
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/iocore/net/P_QUICNetVConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ class QUICNetVConnection : public UnixNetVConnection,
std::unique_ptr<QUICStreamManager> _stream_manager = nullptr;
std::unique_ptr<QUICApplicationMap> _application_map = nullptr;

#if TS_HAS_QUICHE
// Writable-stream count from the previous _handle_write_ready() call, used to size
// this event's per-stream send budget (see QUICStream::compute_fair_send_budget()).
size_t _last_writable_stream_count = 1;
#endif

bool _is_verifying_cert = false;
bool _is_cert_verified = false;
};
Expand Down
6 changes: 5 additions & 1 deletion src/iocore/net/QUICNetProcessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ QUICNetProcessor::start(int, size_t /* stacksize ATS_UNUSED */)
QUICCertConfig::startup();
QUICConfig::scoped_config params;

if (dbg_ctl_vv_quiche.tag_on()) {
// tag_on() only checks the tag pattern, not whether debug output is globally
// enabled -- use on() so a tag that happens to match "vv_quiche" as a substring
// (e.g. "vv_quic") doesn't permanently install quiche's trace-level Rust logger
// regardless of proxy.config.diags.debug.enabled.
if (dbg_ctl_vv_quiche.on()) {
quiche_enable_debug_logging(debug_log, NULL);
}
this->_quiche_config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
Expand Down
7 changes: 6 additions & 1 deletion src/iocore/net/QUICNetVConnection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -688,17 +688,22 @@ void
QUICNetVConnection::_handle_write_ready()
{
if (quiche_conn_is_established(this->_quiche_con)) {
const size_t budget = QUICStream::compute_fair_send_budget(this->_last_writable_stream_count);

quiche_stream_iter *writable = quiche_conn_writable(this->_quiche_con);
uint64_t s = 0;
size_t count = 0;
while (quiche_stream_iter_next(writable, &s)) {
++count;
QUICStream *stream = static_cast<QUICStream *>(this->_stream_manager->find_stream(s));
if (stream == nullptr) {
[[maybe_unused]] QUICConnectionError err;
stream = this->_stream_manager->create_stream(s, err);
}
stream->send_data(*this);
stream->send_data(*this, budget);
}
quiche_stream_iter_free(writable);
this->_last_writable_stream_count = count;
}

Ptr<IOBufferBlock> udp_payload;
Expand Down
7 changes: 6 additions & 1 deletion src/iocore/net/qmux/QMuxConnection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -355,16 +355,21 @@ QMuxConnection::_handle_write_streams()
return;
}

const size_t budget = QUICStream::compute_fair_send_budget(_last_writable_stream_count);

quiche_stream_iter *writable = quiche_conn_writable(_quiche_con);
uint64_t stream_id;
size_t count = 0;

while (quiche_stream_iter_next(writable, &stream_id)) {
++count;
QUICStream *stream = _stream_manager->find_stream(stream_id);
if (stream != nullptr) {
stream->send_data(*this);
stream->send_data(*this, budget);
}
}
quiche_stream_iter_free(writable);
_last_writable_stream_count = count;
}

// --- QUICConnectionInfoProvider ---
Expand Down
Loading