haproxy/src/hq_interop.c

240 lines
4.8 KiB
C
Raw Permalink Normal View History

#include <haproxy/hq_interop.h>
#include <import/ist.h>
#include <haproxy/buf.h>
#include <haproxy/connection.h>
#include <haproxy/dynbuf.h>
#include <haproxy/htx.h>
#include <haproxy/http.h>
#include <haproxy/mux_quic.h>
#include <haproxy/qmux_http.h>
#include <haproxy/qmux_trace.h>
#include <haproxy/trace.h>
static ssize_t hq_interop_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
{
struct htx *htx;
struct htx_sl *sl;
struct buffer htx_buf = BUF_NULL;
struct ist path;
char *ptr = b_head(b);
size_t data = b_data(b);
/* hq-interop parser does not support buffer wrapping. */
BUG_ON(b_data(b) != b_contig_data(b, 0));
/* hq-interop parser is only done once full message is received. */
if (!fin)
return 0;
b_alloc(&htx_buf, DB_MUX_RX);
htx = htx_from_buf(&htx_buf);
/* skip method */
while (data && HTTP_IS_TOKEN(*ptr)) {
ptr++;
data--;
}
if (!data || !HTTP_IS_SPHT(*ptr)) {
fprintf(stderr, "truncated stream\n");
return -1;
}
ptr++;
if (!--data) {
fprintf(stderr, "truncated stream\n");
return -1;
}
if (HTTP_IS_LWS(*ptr)) {
fprintf(stderr, "malformed stream\n");
return -1;
}
/* extract path */
path.ptr = ptr;
while (data && !HTTP_IS_LWS(*ptr)) {
ptr++;
data--;
}
if (!data) {
fprintf(stderr, "truncated stream\n");
return -1;
}
path.len = ptr - path.ptr;
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, 0, ist("GET"), path, ist("HTTP/1.0"));
if (!sl)
return -1;
sl->flags |= HTX_SL_F_BODYLESS;
sl->info.req.meth = find_http_meth("GET", 3);
htx_add_endof(htx, HTX_BLK_EOH);
htx->flags |= HTX_FL_EOM;
htx_to_buf(htx, &htx_buf);
if (!qcs_attach_sc(qcs, &htx_buf, fin))
return -1;
b_free(&htx_buf);
return b_data(b);
}
static size_t hq_interop_snd_buf(struct qcs *qcs, struct buffer *buf,
size_t count)
{
enum htx_blk_type btype;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
struct htx *htx = NULL;
struct htx_blk *blk;
int32_t idx;
uint32_t bsize, fsize;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
struct buffer *res = NULL;
size_t total = 0;
int err;
htx = htx_from_buf(buf);
while (count && !htx_is_empty(htx) && qcc_stream_can_send(qcs)) {
/* Not implemented : QUIC on backend side */
idx = htx_get_head(htx);
blk = htx_get_blk(htx, idx);
btype = htx_get_blk_type(blk);
fsize = bsize = htx_get_blksz(blk);
BUG_ON(btype == HTX_BLK_REQ_SL);
switch (btype) {
case HTX_BLK_DATA:
res = qcc_get_stream_txbuf(qcs, &err);
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
if (!res) {
if (err)
ABORT_NOW();
goto end;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
}
if (unlikely(fsize == count &&
!b_data(res) &&
htx_nbblks(htx) == 1 && btype == HTX_BLK_DATA)) {
void *old_area = res->area;
TRACE_DATA("perform zero-copy DATA transfer", QMUX_EV_STRM_SEND,
qcs->qcc->conn, qcs);
/* remap MUX buffer to HTX area */
*res = b_make(buf->area, buf->size,
sizeof(struct htx) + blk->addr, fsize);
/* assign old MUX area to HTX buffer. */
buf->area = old_area;
buf->data = buf->head = 0;
total += fsize;
/* reload HTX with empty buffer. */
*htx = *htx_from_buf(buf);
goto end;
}
if (fsize > count)
fsize = count;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
if (b_contig_space(res) < fsize)
fsize = b_contig_space(res);
if (!fsize) {
/* Release buf and restart parsing if sending still possible. */
qcc_release_stream_txbuf(qcs);
continue;
}
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
b_putblk(res, htx_get_blk_ptr(htx, blk), fsize);
total += fsize;
count -= fsize;
if (fsize == bsize)
htx_remove_blk(htx, blk);
else
htx_cut_data_blk(htx, blk, fsize);
break;
/* only body is transferred on HTTP/0.9 */
case HTX_BLK_RES_SL:
case HTX_BLK_TLR:
case HTX_BLK_EOT:
default:
htx_remove_blk(htx, blk);
total += bsize;
count -= bsize;
break;
}
}
end:
htx_to_buf(htx, buf);
return total;
}
static size_t hq_interop_nego_ff(struct qcs *qcs, size_t count)
{
int err, ret = 0;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
struct buffer *res;
start:
res = qcc_get_stream_txbuf(qcs, &err);
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
if (!res) {
if (err)
ABORT_NOW();
qcs->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED;
goto end;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
}
if (!b_room(res)) {
if (qcc_release_stream_txbuf(qcs)) {
qcs->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED;
goto end;
}
goto start;
}
/* No header required for HTTP/0.9, no need to reserve an offset. */
qcs->sd->iobuf.buf = res;
qcs->sd->iobuf.offset = 0;
qcs->sd->iobuf.data = 0;
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
ret = MIN(count, b_contig_space(res));
end:
MAJOR: mux-quic: remove intermediary Tx buffer Previously, QUIC MUX sending was implemented with data transfered along two different buffer instances per stream. The first QCS buffer was used for HTX blocks conversion into H3 (or other application protocol) during snd_buf stream callback. QCS instance is then registered for sending via qcc_io_cb(). For each sending QCS, data memcpy is performed from the first to a secondary buffer. A STREAM frame is produced for each QCS based on the content of their secondary buffer. This model is useful for QUIC MUX which has a major difference with other muxes : data must be preserved longer, even after sent to the lower layer. Data references is shared with quic-conn layer which implements retransmission and data deletion on ACK reception. This double buffering stages was the first model implemented and remains active until today. One of its major drawbacks is that it requires memcpy invocation for every data transferred between the two buffers. Another important drawback is that the first buffer was is allocated by each QCS individually without restriction. On the other hand, secondary buffers are accounted for the connection. A bottleneck can appear if secondary buffer pool is exhausted, causing unnecessary haproxy buffering. The purpose of this commit is to completely break this model. The first buffer instance is removed. Now, application protocols will directly allocate buffer from qc_stream_desc layer. This removes completely the memcpy invocation. This commit has a lot of code modifications. The most obvious one is the removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a buffer instance from qc_stream_desc layer. qcs_xfer_data() which was responsible for the memcpy between the two buffers is also completely removed. Offset fields of QCS and QCC are now incremented directly by qcc_send_stream(). These values are used as boundary with flow control real offset to delimit the STREAM frames built. As this change has a big impact on the code, this commit is only the first part to fully support single buffer emission. For the moment, some limitations are reintroduced and will be fixed in the next patches : * on snd_buf if QCS sent buffer in used has room but not enough for the application protocol to store its content * on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds due to connection pool exhaustion One final important aspect is that extra care is necessary now in snd_buf callback. The same buffer instance is referenced by both the stream and quic-conn layer. As such, some operation such as realign cannot be done anymore freely.
2024-01-16 15:47:57 +00:00
return ret;
}
static size_t hq_interop_done_ff(struct qcs *qcs)
{
const size_t ret = qcs->sd->iobuf.data;
/* No header required for HTTP/0.9, simply mark ff as done. */
qcs->sd->iobuf.buf = NULL;
qcs->sd->iobuf.offset = 0;
qcs->sd->iobuf.data = 0;
return ret;
}
MEDIUM: mux-quic: implement http-request timeout Implement http-request timeout for QUIC MUX. It is used when the connection is opened and is triggered if no HTTP request is received in time. By HTTP request we mean at least a QUIC stream with a full header section. Then qcs instance is attached to a sedesc and upper layer is then responsible to wait for the rest of the request. This timeout is also used when new QUIC streams are opened during the connection lifetime to wait for full HTTP request on them. As it's possible to demux multiple streams in parallel with QUIC, each waiting stream is registered in a list <opening_list> stored in qcc with <start> as timestamp in qcs for the stream opening. Once a qcs is attached to a sedesc, it is removed from <opening_list>. When refreshing MUX timeout, if <opening_list> is not empty, the first waiting stream is used to set MUX timeout. This is efficient as streams are stored in the list in their creation order so CPU usage is minimal. Also, the size of the list is automatically restricted by flow control limitation so it should not grow too much. Streams are insert in <opening_list> by application protocol layer. This is because only application protocol can differentiate streams for HTTP messaging from internal usage. A function qcs_wait_http_req() has been added to register a request stream by app layer. QUIC MUX can then remove it from the list in qc_attach_sc(). As a side-note, it was necessary to implement attach qcc_app_ops callback on hq-interop module to be able to insert a stream in waiting list. Without this, a BUG_ON statement would be triggered when trying to remove the stream on sedesc attach. This is to ensure that every requests streams are registered for http-request timeout. MUX timeout is explicitely refreshed on MAX_STREAM_DATA and STOP_SENDING frame parsing to schedule http-request timeout if a new stream has been instantiated. It was already done on STREAM parsing due to a previous patch.
2022-08-03 09:17:57 +00:00
static int hq_interop_attach(struct qcs *qcs, void *conn_ctx)
{
qcs_wait_http_req(qcs);
return 0;
}
const struct qcc_app_ops hq_interop_ops = {
.rcv_buf = hq_interop_rcv_buf,
.snd_buf = hq_interop_snd_buf,
.nego_ff = hq_interop_nego_ff,
.done_ff = hq_interop_done_ff,
MEDIUM: mux-quic: implement http-request timeout Implement http-request timeout for QUIC MUX. It is used when the connection is opened and is triggered if no HTTP request is received in time. By HTTP request we mean at least a QUIC stream with a full header section. Then qcs instance is attached to a sedesc and upper layer is then responsible to wait for the rest of the request. This timeout is also used when new QUIC streams are opened during the connection lifetime to wait for full HTTP request on them. As it's possible to demux multiple streams in parallel with QUIC, each waiting stream is registered in a list <opening_list> stored in qcc with <start> as timestamp in qcs for the stream opening. Once a qcs is attached to a sedesc, it is removed from <opening_list>. When refreshing MUX timeout, if <opening_list> is not empty, the first waiting stream is used to set MUX timeout. This is efficient as streams are stored in the list in their creation order so CPU usage is minimal. Also, the size of the list is automatically restricted by flow control limitation so it should not grow too much. Streams are insert in <opening_list> by application protocol layer. This is because only application protocol can differentiate streams for HTTP messaging from internal usage. A function qcs_wait_http_req() has been added to register a request stream by app layer. QUIC MUX can then remove it from the list in qc_attach_sc(). As a side-note, it was necessary to implement attach qcc_app_ops callback on hq-interop module to be able to insert a stream in waiting list. Without this, a BUG_ON statement would be triggered when trying to remove the stream on sedesc attach. This is to ensure that every requests streams are registered for http-request timeout. MUX timeout is explicitely refreshed on MAX_STREAM_DATA and STOP_SENDING frame parsing to schedule http-request timeout if a new stream has been instantiated. It was already done on STREAM parsing due to a previous patch.
2022-08-03 09:17:57 +00:00
.attach = hq_interop_attach,
};