[nginx] QUIC: CUBIC congestion control.
noreply at nginx.com
noreply at nginx.com
Tue Apr 15 15:02:03 UTC 2025
details: https://github.com/nginx/nginx/commit/f9a7e7cc11e71b2c62d4c5b9ac4feb7e92913c64
branches: master
commit: f9a7e7cc11e71b2c62d4c5b9ac4feb7e92913c64
user: Roman Arutyunyan <arut at nginx.com>
date: Thu, 7 Nov 2024 17:25:45 +0400
description:
QUIC: CUBIC congestion control.
---
src/event/quic/ngx_event_quic.c | 1 +
src/event/quic/ngx_event_quic_ack.c | 189 +++++++++++++++++++++++++++--
src/event/quic/ngx_event_quic_connection.h | 6 +
src/event/quic/ngx_event_quic_migration.c | 1 +
4 files changed, 185 insertions(+), 12 deletions(-)
diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
index 11497a6d7..49d30e82a 100644
--- a/src/event/quic/ngx_event_quic.c
+++ b/src/event/quic/ngx_event_quic.c
@@ -312,6 +312,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
ngx_max(2 * NGX_QUIC_MIN_INITIAL_SIZE,
14720));
qc->congestion.ssthresh = (size_t) -1;
+ qc->congestion.mtu = NGX_QUIC_MIN_INITIAL_SIZE;
qc->congestion.recovery_start = ngx_current_msec - 1;
if (pkt->validated && pkt->retried) {
diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c
index d16545a1d..6b0eef35e 100644
--- a/src/event/quic/ngx_event_quic_ack.c
+++ b/src/event/quic/ngx_event_quic_ack.c
@@ -20,6 +20,10 @@
/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */
#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3
+/* CUBIC parameters x10 */
+#define NGX_QUIC_CUBIC_BETA 7
+#define MGX_QUIC_CUBIC_C 4
+
/* send time of ACK'ed packets */
typedef struct {
@@ -35,10 +39,12 @@ static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max,
ngx_quic_ack_stat_t *st);
+static size_t ngx_quic_congestion_cubic(ngx_connection_t *c);
static void ngx_quic_drop_ack_ranges(ngx_connection_t *c,
ngx_quic_send_ctx_t *ctx, uint64_t pn);
static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c,
ngx_quic_ack_stat_t *st);
+static ngx_msec_t ngx_quic_congestion_cubic_time(ngx_connection_t *c);
static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c);
static void ngx_quic_persistent_congestion(ngx_connection_t *c);
static ngx_msec_t ngx_quic_oldest_sent_packet(ngx_connection_t *c);
@@ -314,6 +320,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
void
ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
{
+ size_t w_cubic;
ngx_uint_t blocked;
ngx_msec_t now, timer;
ngx_quic_congestion_t *cg;
@@ -370,11 +377,46 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
now, cg->window, cg->ssthresh, cg->in_flight);
} else {
- cg->window += (uint64_t) qc->path->mtu * f->plen / cg->window;
- ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
- "quic congestion ack reno t:%M win:%uz if:%uz",
- now, cg->window, cg->in_flight);
+ /* RFC 9438, 4.2. Window Increase Function */
+
+ w_cubic = ngx_quic_congestion_cubic(c);
+
+ if (cg->window < cg->w_prior) {
+ cg->w_est += (uint64_t) cg->mtu * f->plen
+ * 3 * (10 - NGX_QUIC_CUBIC_BETA)
+ / (10 + NGX_QUIC_CUBIC_BETA) / cg->window;
+
+ } else {
+ cg->w_est += (uint64_t) cg->mtu * f->plen / cg->window;
+ }
+
+ if (w_cubic < cg->w_est) {
+ cg->window = cg->w_est;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion ack reno t:%M win:%uz c:%uz if:%uz",
+ now, cg->window, w_cubic, cg->in_flight);
+
+ } else if (w_cubic > cg->window) {
+
+ if (w_cubic >= cg->window * 3 / 2) {
+ cg->window += cg->mtu / 2;
+
+ } else {
+ cg->window += (uint64_t) cg->mtu * (w_cubic - cg->window)
+ / cg->window;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion ack cubic t:%M win:%uz c:%uz if:%uz",
+ now, cg->window, w_cubic, cg->in_flight);
+
+ } else {
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion ack skip t:%M win:%uz c:%uz if:%uz",
+ now, cg->window, w_cubic, cg->in_flight);
+ }
}
done:
@@ -385,9 +427,62 @@ done:
}
+static size_t
+ngx_quic_congestion_cubic(ngx_connection_t *c)
+{
+ int64_t w, t, cc;
+ ngx_msec_t now;
+ ngx_quic_congestion_t *cg;
+ ngx_quic_connection_t *qc;
+
+ qc = ngx_quic_get_connection(c);
+ cg = &qc->congestion;
+
+ ngx_quic_congestion_idle(c, cg->idle);
+
+ now = ngx_current_msec;
+ t = (ngx_msec_int_t) (now - cg->k);
+
+ if (t > 1000000) {
+ w = NGX_MAX_SIZE_T_VALUE;
+ goto done;
+ }
+
+ if (t < -1000000) {
+ w = 0;
+ goto done;
+ }
+
+ /*
+ * RFC 9438, Figure 1
+ *
+ * w_cubic = C * (t_msec / 1000) ^ 3 * mtu + w_max
+ */
+
+ cc = 10000000000ll / (int64_t) cg->mtu / MGX_QUIC_CUBIC_C;
+ w = t * t * t / cc + (int64_t) cg->w_max;
+
+ if (w > NGX_MAX_SIZE_T_VALUE) {
+ w = NGX_MAX_SIZE_T_VALUE;
+ }
+
+ if (w < 0) {
+ w = 0;
+ }
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic cubic t:%L w:%L wm:%uz", t, w, cg->w_max);
+
+ return w;
+}
+
+
void
ngx_quic_congestion_idle(ngx_connection_t *c, ngx_uint_t idle)
{
+ ngx_msec_t now;
ngx_quic_congestion_t *cg;
ngx_quic_connection_t *qc;
@@ -397,6 +492,18 @@ ngx_quic_congestion_idle(ngx_connection_t *c, ngx_uint_t idle)
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion idle:%ui", idle);
+ if (cg->window >= cg->ssthresh) {
+ /* RFC 9438, 5.8. Behavior for Application-Limited Flows */
+
+ now = ngx_current_msec;
+
+ if (cg->idle) {
+ cg->k += now - cg->idle_start;
+ }
+
+ cg->idle_start = now;
+ }
+
cg->idle = idle;
}
@@ -580,8 +687,9 @@ ngx_quic_persistent_congestion(ngx_connection_t *c)
qc = ngx_quic_get_connection(c);
cg = &qc->congestion;
+ cg->mtu = qc->path->mtu;
cg->recovery_start = ngx_quic_oldest_sent_packet(c) - 1;
- cg->window = qc->path->mtu * 2;
+ cg->window = cg->mtu * 2;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion persistent t:%M win:%uz",
@@ -763,14 +871,19 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f)
goto done;
}
- cg->recovery_start = now;
- cg->window /= 2;
-
- if (cg->window < qc->path->mtu * 2) {
- cg->window = qc->path->mtu * 2;
- }
+ /* RFC 9438, 4.6. Multiplicative Decrease */
- cg->ssthresh = cg->window;
+ cg->mtu = qc->path->mtu;
+ cg->recovery_start = now;
+ cg->w_prior = cg->window;
+ /* RFC 9438, 4.7. Fast Convergence */
+ cg->w_max = (cg->window < cg->w_max)
+ ? cg->window * (10 + NGX_QUIC_CUBIC_BETA) / 20 : cg->window;
+ cg->ssthresh = cg->in_flight * NGX_QUIC_CUBIC_BETA / 10;
+ cg->window = ngx_max(cg->ssthresh, cg->mtu * 2);
+ cg->w_est = cg->window;
+ cg->k = now + ngx_quic_congestion_cubic_time(c);
+ cg->idle_start = now;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic congestion lost t:%M win:%uz if:%uz",
@@ -784,6 +897,58 @@ done:
}
+static ngx_msec_t
+ngx_quic_congestion_cubic_time(ngx_connection_t *c)
+{
+ int64_t v, x, d, cc;
+ ngx_uint_t n;
+ ngx_quic_congestion_t *cg;
+ ngx_quic_connection_t *qc;
+
+ qc = ngx_quic_get_connection(c);
+ cg = &qc->congestion;
+
+ /*
+ * RFC 9438, Figure 2
+ *
+ * k_msec = ((w_max - cwnd_epoch) / C / mtu) ^ 1/3 * 1000
+ */
+
+ if (cg->w_max <= cg->window) {
+ return 0;
+ }
+
+ cc = 10000000000ll / (int64_t) cg->mtu / MGX_QUIC_CUBIC_C;
+ v = (int64_t) (cg->w_max - cg->window) * cc;
+
+ /*
+ * Newton-Raphson method for x ^ 3 = v:
+ *
+ * x_next = (2 * x_prev + v / x_prev ^ 2) / 3
+ */
+
+ x = 5000;
+
+ for (n = 1; n <= 10; n++) {
+ d = (v / x / x - x) / 3;
+ x += d;
+
+ if (ngx_abs(d) <= 100) {
+ break;
+ }
+ }
+
+ if (x > NGX_MAX_SIZE_T_VALUE) {
+ return NGX_MAX_SIZE_T_VALUE;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic cubic time:%L n:%ui", x, n);
+
+ return x;
+}
+
+
void
ngx_quic_set_lost_timer(ngx_connection_t *c)
{
diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h
index acc09c142..716d62308 100644
--- a/src/event/quic/ngx_event_quic_connection.h
+++ b/src/event/quic/ngx_event_quic_connection.h
@@ -168,7 +168,13 @@ typedef struct {
size_t in_flight;
size_t window;
size_t ssthresh;
+ size_t w_max;
+ size_t w_est;
+ size_t w_prior;
+ size_t mtu;
ngx_msec_t recovery_start;
+ ngx_msec_t idle_start;
+ ngx_msec_t k;
ngx_uint_t idle; /* unsigned idle:1; */
} ngx_quic_congestion_t;
diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c
index 1d914ffd8..6befc3427 100644
--- a/src/event/quic/ngx_event_quic_migration.c
+++ b/src/event/quic/ngx_event_quic_migration.c
@@ -186,6 +186,7 @@ valid:
ngx_max(2 * NGX_QUIC_MIN_INITIAL_SIZE,
14720));
qc->congestion.ssthresh = (size_t) -1;
+ qc->congestion.mtu = NGX_QUIC_MIN_INITIAL_SIZE;
qc->congestion.recovery_start = ngx_current_msec - 1;
ngx_quic_init_rtt(qc);
More information about the nginx-devel
mailing list