Preserve BBR RTT filter age and full-width probe deadlines
This commit is contained in:
1 parent
deee734e6c
commit
bb462bad69
3 files changed
+137
-19
No files matched your search
@@ -577,36 +577,42 @@ static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
|
||||
static void bbr_check_probe_rtt_done(struct bbr* bbr, uint32_t* cwnd)
|
||||
{
|
||||
if (!bbr->probe_rtt_done_stamp_tb)
|
||||
uint64_t now = bbr_now(bbr);
|
||||
if (!bbr->probe_rtt_done_stamp_tb || now < bbr->probe_rtt_done_stamp_tb)
|
||||
return;
|
||||
if (!((int32_t)((uint32_t)bbr_now(bbr) - (uint32_t)bbr->probe_rtt_done_stamp_tb) >= 0))
|
||||
return;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] PROBE_RTT complete now_tb=%llu deadline_tb=%llu min_us=%u probe_min_us=%u",
|
||||
bbr->link_id, (unsigned long long)now, (unsigned long long)bbr->probe_rtt_done_stamp_tb,
|
||||
bbr->min_rtt_us, bbr->probe_rtt_min_us);
|
||||
bbr->probe_rtt_min_stamp_tb = now;
|
||||
*cwnd = (uint32_t)(*cwnd > bbr->prior_cwnd ? *cwnd : bbr->prior_cwnd);
|
||||
bbr_exit_probe_rtt(bbr);
|
||||
}
|
||||
|
||||
/* Обновить RTT-фильтры с наследованием возраста минимума и цикл PROBE_RTT по полным 64-битным часам. */
|
||||
static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd, uint32_t mss, uint32_t inflight_bytes)
|
||||
{
|
||||
uint32_t expire;
|
||||
uint64_t now = bbr_now(bbr);
|
||||
int probe_rtt_expired, min_rtt_expired;
|
||||
|
||||
expire = (uint32_t)(bbr->probe_rtt_min_stamp_tb + BBR_PROBE_RTT_WIN_MS * 10U);
|
||||
probe_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
probe_rtt_expired = now - bbr->probe_rtt_min_stamp_tb > BBR_PROBE_RTT_WIN_MS * 10ULL;
|
||||
if (rs->rtt_us > 0 &&
|
||||
(rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) {
|
||||
bbr->probe_rtt_min_us = rs->rtt_us;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->probe_rtt_min_stamp_tb = now;
|
||||
}
|
||||
if (bbr->probe_rtt_min_us == 0)
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
|
||||
expire = (uint32_t)(bbr->min_rtt_stamp_tb + BBR_MIN_RTT_WIN_SEC * 10000U);
|
||||
min_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
min_rtt_expired = now - bbr->min_rtt_stamp_tb > BBR_MIN_RTT_WIN_SEC * 10000ULL;
|
||||
if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || min_rtt_expired) {
|
||||
if (bbr->min_rtt_us != bbr->probe_rtt_min_us || bbr->min_rtt_stamp_tb != bbr->probe_rtt_min_stamp_tb)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] min RTT: %u->%u sample_us=%u stamp_tb=%llu->%llu now_tb=%llu expired=%d",
|
||||
bbr->link_id, bbr->min_rtt_us, bbr->probe_rtt_min_us, rs->rtt_us,
|
||||
(unsigned long long)bbr->min_rtt_stamp_tb, (unsigned long long)bbr->probe_rtt_min_stamp_tb,
|
||||
(unsigned long long)now, min_rtt_expired);
|
||||
bbr->min_rtt_us = bbr->probe_rtt_min_us;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
bbr->min_rtt_stamp_tb = bbr->probe_rtt_min_stamp_tb;
|
||||
}
|
||||
|
||||
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
|
||||
@@ -623,11 +629,12 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
if (!bbr->probe_rtt_done_stamp_tb &&
|
||||
inflight_bytes <= bbr_probe_rtt_cwnd(bbr, mss)) {
|
||||
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
|
||||
bbr->probe_rtt_done_stamp_tb = now + BBR_PROBE_RTT_MS * 10ULL;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] PROBE_RTT drained inflight=%u target=%u hold_ms=%u",
|
||||
bbr->link_id, inflight_bytes, bbr_probe_rtt_cwnd(bbr, mss), BBR_PROBE_RTT_MS);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] PROBE_RTT drained inflight=%u target=%u hold_ms=%u deadline_tb=%llu",
|
||||
bbr->link_id, inflight_bytes, bbr_probe_rtt_cwnd(bbr, mss), BBR_PROBE_RTT_MS,
|
||||
(unsigned long long)bbr->probe_rtt_done_stamp_tb);
|
||||
} else if (bbr->probe_rtt_done_stamp_tb) {
|
||||
if (bbr->round_start)
|
||||
bbr->probe_rtt_round_done = 1;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
* cwnd/inflight/delivered — байты DATA, interval/rtt — микросекунды, pacing_rate — байты в секунду.
|
||||
* MSS — максимальный DATA-фрагмент normalizer, без заголовков; не wire-MTU и не размер кадра STCP.
|
||||
* Первый достоверный RTT в STARTUP инициализирует pacing от начальных 4*MSS, включая короткие служебные DATA.
|
||||
* Короткий RTT-минимум обновляется меньшим достоверным RTT либо измерением после истечения 5с.
|
||||
* min_rtt наследует его значение и метку; большее значение принимается после 10с возраста min_rtt.
|
||||
* Выход из PROBE_RTT возобновляет короткое окно. Копирование кеша сохраняет его метку, не задаёт текущее время.
|
||||
* Метки RTT-фильтров и срок hold — uint64 TB (100мкс); окна проверяются при обработке ACK.
|
||||
* Минимальное окно — 4*MSS; PROBE_RTT осушает до max(4*MSS, 0.5*BDP), затем ждёт >=200мс и раунд ACK.
|
||||
* Транспорт применяет окно с верхним ограничением конфига и не повышает его до собственного минимума.
|
||||
* Целевое окно PROBE_BW включает 2*BDP и ACK-резерв, в PROBE_UP дополнительно 2*MSS.
|
||||
@@ -67,7 +71,7 @@ struct bbr_rate_sample {
|
||||
typedef void (*bbr_cwnd_update_fn)(void* ctx, uint32_t new_cwnd);
|
||||
|
||||
struct bbr {
|
||||
uint32_t min_rtt_us;
|
||||
uint32_t min_rtt_us; // принятый долгий минимум RTT, мкс; UINT32_MAX — измерений ещё нет
|
||||
uint32_t bw_lo;
|
||||
uint32_t bw_hi[2];
|
||||
uint32_t inflight_lo;
|
||||
@@ -100,9 +104,9 @@ struct bbr {
|
||||
uint8_t rounds_since_probe;
|
||||
uint32_t probe_wait_us;
|
||||
uint64_t cycle_mstamp_tb;
|
||||
uint32_t probe_rtt_min_us;
|
||||
uint64_t probe_rtt_min_stamp_tb;
|
||||
uint64_t probe_rtt_done_stamp_tb;
|
||||
uint32_t probe_rtt_min_us; // короткий RTT-минимум, мкс; UINT32_MAX — измерений ещё нет
|
||||
uint64_t probe_rtt_min_stamp_tb; // метка короткого фильтра; обновляется его измерением и завершением PROBE_RTT
|
||||
uint64_t probe_rtt_done_stamp_tb; // полный срок hold в TB; 0 — hold ещё не начат; используется в PROBE_RTT
|
||||
uint8_t probe_rtt_round_done : 1;
|
||||
uint32_t extra_acked[2]; // избыточно подтверждённые байты, без усечения на 64KiB
|
||||
uint32_t ack_epoch_acked;
|
||||
@@ -116,7 +120,7 @@ struct bbr {
|
||||
uint8_t loss_events_in_round : 4;
|
||||
uint32_t delivered;
|
||||
uint32_t full_bw;
|
||||
uint64_t min_rtt_stamp_tb;
|
||||
uint64_t min_rtt_stamp_tb; // метка короткого фильтра, принятая вместе с min_rtt_us; окно 10с
|
||||
uint8_t loss_round_start : 1;
|
||||
uint8_t full_bw_now : 1;
|
||||
uint8_t pad_unused : 6;
|
||||
|
||||
@@ -261,6 +261,87 @@ static int test_probe_rtt(void)
|
||||
PASS(); return 0;
|
||||
}
|
||||
|
||||
/* Повышение RTT не омолаживает старый минимум; проверяем основной/быстрый путь и границу 32 бит TB. */
|
||||
static int test_min_rtt_lifetime(void) {
|
||||
MSEG("min RTT expires after 10s, including fast path and TB wrap");
|
||||
const uint64_t starts[] = {10000, (uint64_t)UINT32_MAX-30000, UINT64_C(1)<<40};
|
||||
for (unsigned fast = 0; fast < 2; fast++) for (unsigned i = 0; i < sizeof(starts)/sizeof(starts[0]); i++) {
|
||||
struct bbr s = {.now_tb=starts[i]}; bbr_init(&s);
|
||||
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
|
||||
s.bw_hi[0] = BW_UNIT/10;
|
||||
uint32_t cwnd = 10000, pacing = 0, replaced_ms = 0;
|
||||
uint64_t cached_stamp = 0;
|
||||
struct bbr_rate_sample rs = {.delivered=1000,.interval_us=100000,.rtt_us=20000,
|
||||
.acked_sacked=1000,.tx_in_flight=1000,.is_app_limited=fast};
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
rs.rtt_us = 100000;
|
||||
for (unsigned ms = 100; ms <= 16000; ms += 100) {
|
||||
s.now_tb = starts[i]+ms*10; s.try_fast_path = fast; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
if (ms == 4900) cached_stamp = s.min_rtt_stamp_tb;
|
||||
if (!replaced_ms && s.min_rtt_us == 100000) replaced_ms = ms;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTT lifetime: start_tb=%llu fast=%u cached_stamp_tb=%llu replaced_ms=%u expected_ms=10100",
|
||||
(unsigned long long)starts[i],fast,(unsigned long long)cached_stamp,replaced_ms);
|
||||
if (cached_stamp != starts[i]) FAIL("cached minimum was timestamped as a new RTT observation");
|
||||
if (replaced_ms != 10100) FAIL("20ms minimum was not replaced just after its 10s window");
|
||||
}
|
||||
PASS(); return 0;
|
||||
}
|
||||
|
||||
/* Граница 10с строгая; реальное новое измерение такого же минимума продлевает его жизнь. */
|
||||
static int test_min_rtt_refresh(void) {
|
||||
MSEG("min RTT boundary, fresh equal/lower samples and unknown RTT");
|
||||
for (unsigned fresh_equal = 0; fresh_equal < 2; fresh_equal++) {
|
||||
struct bbr s = {.now_tb=10000}; bbr_init(&s);
|
||||
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
|
||||
uint32_t cwnd = 10000, pacing = 0;
|
||||
struct bbr_rate_sample rs = {.delivered=1000,.interval_us=100000,.rtt_us=20000,
|
||||
.acked_sacked=1000,.tx_in_flight=1000};
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
s.now_tb = 60001; rs.rtt_us = fresh_equal ? 20000 : 100000; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
s.now_tb = 62001; rs.rtt_us = 100000; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0); // завершаем hold и ACK-раунд PROBE_RTT
|
||||
uint64_t origin = fresh_equal ? 60001 : 10000;
|
||||
s.now_tb = origin+100000; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTT boundary: fresh_equal=%u now_tb=%llu origin_tb=%llu min_us=%u stamp_tb=%llu",
|
||||
fresh_equal,(unsigned long long)s.now_tb,(unsigned long long)origin,s.min_rtt_us,
|
||||
(unsigned long long)s.min_rtt_stamp_tb);
|
||||
if (s.min_rtt_us != 20000) FAIL("minimum expired at the exact boundary");
|
||||
s.now_tb++; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
if (s.min_rtt_us != 100000) FAIL("minimum did not expire one TB after the boundary");
|
||||
s.now_tb++; rs.rtt_us = 15000; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
uint64_t lower_stamp = s.min_rtt_stamp_tb;
|
||||
if (s.min_rtt_us != 15000 || lower_stamp != s.now_tb) FAIL("fresh lower RTT did not replace the minimum immediately");
|
||||
s.now_tb += 1000; rs.rtt_us = 0; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
if (s.min_rtt_us != 15000 || s.min_rtt_stamp_tb != lower_stamp) FAIL("unknown RTT changed the minimum or its age");
|
||||
}
|
||||
PASS(); return 0;
|
||||
}
|
||||
|
||||
/* Большой перерыв не теряется при сравнении младших 32 бит часов. */
|
||||
static int test_min_rtt_long_idle(void) {
|
||||
MSEG("RTT filters expire after an idle longer than 32-bit TB range");
|
||||
struct bbr s = {.now_tb=10000}; bbr_init(&s);
|
||||
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
|
||||
uint32_t cwnd = 10000, pacing = 0;
|
||||
struct bbr_rate_sample rs = {.delivered=1000,.interval_us=100000,.rtt_us=20000,
|
||||
.acked_sacked=1000,.tx_in_flight=1000};
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
s.now_tb += UINT64_C(1)<<32; rs.rtt_us = 100000; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTT long idle: now_tb=%llu min_us=%u probe_us=%u mode=%u",
|
||||
(unsigned long long)s.now_tb,s.min_rtt_us,s.probe_rtt_min_us,s.mode);
|
||||
if (s.min_rtt_us != 100000 || s.probe_rtt_min_us != 100000 || s.mode != BBR_PROBE_RTT)
|
||||
FAIL("old RTT survived an idle of a full 32-bit TB range");
|
||||
PASS(); return 0;
|
||||
}
|
||||
|
||||
/* Длинный канал осушается до половины BDP, затем выдерживает 200мс и полный раунд доставки. */
|
||||
static int test_probe_rtt_lifecycle(void) {
|
||||
MSEG("PROBE_RTT half-BDP drain, hold and window restore");
|
||||
@@ -289,6 +370,28 @@ static int test_probe_rtt_lifecycle(void) {
|
||||
PASS(); return 0;
|
||||
}
|
||||
|
||||
/* Нулевой младший uint32 срока не означает отсутствие hold; ждать надо полные 200мс. */
|
||||
static int test_probe_rtt_deadline_wrap(void) {
|
||||
MSEG("PROBE_RTT hold at a zero low-32-bit deadline");
|
||||
struct bbr s = {.now_tb=(UINT64_C(1)<<32)-2000}; bbr_init(&s);
|
||||
s.mode = BBR_PROBE_RTT; s.full_bw_reached = 1;
|
||||
uint32_t cwnd = 10000, pacing = 0;
|
||||
struct bbr_rate_sample rs = {.delivered=1000,.interval_us=100000,.rtt_us=20000,
|
||||
.acked_sacked=1000,.tx_in_flight=1000,.is_app_limited=1};
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PROBE_RTT zero deadline: now_tb=%llu deadline_tb=%llu expected_tb=%llu",
|
||||
(unsigned long long)s.now_tb,(unsigned long long)s.probe_rtt_done_stamp_tb,
|
||||
(unsigned long long)(UINT64_C(1)<<32));
|
||||
if (!s.probe_rtt_done_stamp_tb) FAIL("zero low bits erased the active PROBE_RTT deadline");
|
||||
s.now_tb += 1999; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
if (s.mode != BBR_PROBE_RTT || !s.probe_rtt_round_done) FAIL("probe must hold for 200ms even with a complete round");
|
||||
s.now_tb++; rs.prior_delivered = s.delivered;
|
||||
bbr_main(&s,&rs,&cwnd,&pacing,1400,1000,0);
|
||||
if (s.mode != BBR_PROBE_BW) FAIL("probe did not finish at the 200ms deadline across TB wrap");
|
||||
PASS(); return 0;
|
||||
}
|
||||
|
||||
/* Бюджет включает все резервы окна и не зависит от удлинения текущего RTT очередью. */
|
||||
static int test_queue_budget(void) {
|
||||
MSEG("queue time budget includes ACK and probing reserves");
|
||||
@@ -427,7 +530,11 @@ int main(void)
|
||||
test_loss_cut();
|
||||
test_fast_path();
|
||||
test_probe_rtt();
|
||||
test_min_rtt_lifetime();
|
||||
test_min_rtt_refresh();
|
||||
test_min_rtt_long_idle();
|
||||
test_probe_rtt_lifecycle();
|
||||
test_probe_rtt_deadline_wrap();
|
||||
test_queue_budget();
|
||||
test_queue_budget_floor_and_startup();
|
||||
test_queue_budget_fast_path();
|
||||
|
||||
Reference in new issue
Block a user