QUIC: CUBIC congestion control.

This commit is contained in:
Roman Arutyunyan 2024-11-07 17:25:45 +04:00 committed by Roman Arutyunyan
parent a40cc70023
commit f9a7e7cc11
4 changed files with 185 additions and 12 deletions

View File

@ -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) {

View File

@ -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;
}
/* RFC 9438, 4.6. Multiplicative Decrease */
cg->mtu = qc->path->mtu;
cg->recovery_start = now;
cg->window /= 2;
if (cg->window < qc->path->mtu * 2) {
cg->window = qc->path->mtu * 2;
}
cg->ssthresh = cg->window;
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)
{

View File

@ -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;

View File

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