summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmaury Denoyelle <adenoyelle@haproxy.com>2023-02-28 15:11:09 +0100
committerAmaury Denoyelle <adenoyelle@haproxy.com>2023-03-01 14:32:37 +0100
commite1a0ee3cf6bd1487b17cfd134b6731c18452a832 (patch)
tree18249ab9266961045e35f543cb77c679276eea0c
parent147862de6199542fa95f2203ec7617e61de9e181 (diff)
downloadhaproxy-e1a0ee3cf6bd1487b17cfd134b6731c18452a832.tar.gz
MEDIUM: quic: implement poller subscribe on sendto error
On sendto() transient error, prior to this patch sending was simulated and we relied on retransmission to retry sending. This could hurt significantly the performance. Thanks to quic-conn owned socket support, it is now possible to improve this. On transient error, sending is interrupted and quic-conn socket FD is subscribed on the poller for sending. When send is possible, quic_conn_sock_fd_iocb() will be in charge of restart sending. A consequence of this change is on the return value of qc_send_ppkts(). This function will now return 0 on transient error if quic-conn has its owned socket. This is used to interrupt sending in the calling function. The flag QUIC_FL_CONN_TO_KILL must be checked to differentiate a fatal error from a transient one. This should be backported up to 2.7.
-rw-r--r--doc/configuration.txt5
-rw-r--r--src/quic_conn.c23
-rw-r--r--src/quic_sock.c17
3 files changed, 36 insertions, 9 deletions
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 208f98784..e7a399343 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3176,8 +3176,9 @@ tune.quic.socket-owner { listener | connection }
When default "connection" value is set, a dedicated socket will be allocated
by every QUIC connections. This option is the preferred one to achieve the
best performance with a large QUIC traffic. This is also the only way to
- ensure soft-stop is conducted properly without data loss for QUIC
- connections. However, this relies on some advanced features from the UDP
+ ensure soft-stop is conducted properly without data loss for QUIC connections
+ and cases of transient errors during sendto() operation are handled
+ efficiently. However, this relies on some advanced features from the UDP
network stack. If your platform is deemed not compatible, haproxy will
automatically switch to "listener" mode on startup.
diff --git a/src/quic_conn.c b/src/quic_conn.c
index d97e82eee..1021bb130 100644
--- a/src/quic_conn.c
+++ b/src/quic_conn.c
@@ -3436,8 +3436,10 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf,
*
* This function returns 1 for success. On error, there is several behavior
* depending on underlying sendto() error :
- * - for a fatal error, 0 is returned and connection is killed.
- * - a transient error is assimilated to a success case with 1 returned.
+ * - for an unrecoverable error, 0 is returned and connection is killed.
+ * - a transient error is handled differently if connection has its owned
+ * socket. If this is the case, 0 is returned and socket is subscribed on the
+ * poller. The other case is assimilated to a success case with 1 returned.
* Remaining data are purged from the buffer and will eventually be detected
* as lost which gives the opportunity to retry sending.
*/
@@ -3481,11 +3483,19 @@ int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
if (!skip_sendto) {
int ret = qc_snd_buf(qc, &tmpbuf, tmpbuf.data, 0);
if (ret < 0) {
+ TRACE_ERROR("sendto fatal error", QUIC_EV_CONN_SPPKTS, qc);
qc_kill_conn(qc);
b_del(buf, buf->data);
goto leave;
}
else if (!ret) {
+ /* Connection owned socket : poller will wake us up when transient error is cleared. */
+ if (qc_test_fd(qc)) {
+ TRACE_ERROR("sendto error, subscribe to poller", QUIC_EV_CONN_SPPKTS, qc);
+ goto leave;
+ }
+
+ /* No connection owned-socket : rely on retransmission to retry sending. */
skip_sendto = 1;
TRACE_ERROR("sendto error, simulate sending for the rest of data", QUIC_EV_CONN_SPPKTS, qc);
}
@@ -4260,7 +4270,8 @@ static int qc_send_app_pkts(struct quic_conn *qc, struct list *frms)
break;
if (!qc_send_ppkts(buf, qc->xprt_ctx)) {
- qc_txb_release(qc);
+ if (qc->flags & QUIC_FL_CONN_TO_KILL)
+ qc_txb_release(qc);
goto err;
}
}
@@ -4359,7 +4370,8 @@ int qc_send_hdshk_pkts(struct quic_conn *qc, int old_data,
}
if (ret && !qc_send_ppkts(buf, qc->xprt_ctx)) {
- qc_txb_release(qc);
+ if (qc->flags & QUIC_FL_CONN_TO_KILL)
+ qc_txb_release(qc);
goto out;
}
@@ -4665,7 +4677,8 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
}
if (ret && !qc_send_ppkts(buf, qc->xprt_ctx)) {
- qc_txb_release(qc);
+ if (qc->flags & QUIC_FL_CONN_TO_KILL)
+ qc_txb_release(qc);
goto out;
}
diff --git a/src/quic_sock.c b/src/quic_sock.c
index 77a2a89f5..8c99a76a6 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -492,8 +492,16 @@ static void quic_conn_sock_fd_iocb(int fd)
TRACE_ENTER(QUIC_EV_CONN_RCV, qc);
- tasklet_wakeup_after(NULL, qc->wait_event.tasklet);
- fd_stop_recv(fd);
+ if (fd_send_active(fd) && fd_send_ready(fd)) {
+ TRACE_DEVEL("send ready", QUIC_EV_CONN_RCV, qc);
+ fd_stop_send(fd);
+ tasklet_wakeup_after(NULL, qc->wait_event.tasklet);
+ }
+
+ if (fd_recv_ready(fd)) {
+ tasklet_wakeup_after(NULL, qc->wait_event.tasklet);
+ fd_stop_recv(fd);
+ }
TRACE_LEAVE(QUIC_EV_CONN_RCV, qc);
}
@@ -516,6 +524,9 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
do {
if (qc_test_fd(qc)) {
+ if (!fd_send_ready(qc->fd))
+ return 0;
+
ret = send(qc->fd, b_peek(buf, b_head_ofs(buf)), sz,
MSG_DONTWAIT | MSG_NOSIGNAL);
}
@@ -626,6 +637,8 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
HA_ATOMIC_INC(&prx_counters->sendto_err);
/* transient error */
+ fd_want_send(qc->fd);
+ fd_cant_send(qc->fd);
TRACE_PRINTF(TRACE_LEVEL_USER, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0,
"UDP send failure errno=%d (%s)", errno, strerror(errno));
return 0;