Merge pull request #1138 from LibreQoE/fix/htb-lazy-full-reload

Fix HTB-lazy full reload queue growth
This commit is contained in:
Robert Chacón authored and GitHub committed 2026-08-01 07:34:36 -06:00
commit 5d0f94fd2d
2 files changed
+68 -31

No files matched your search

+5 -1
View File
@@ -298,7 +298,11 @@ Best use case is asymmetric links where upstream ACK load limits downstream good
`autorate-ingress` can estimate capacity from arriving traffic and is primarily useful on highly variable links (for example some cellular paths). It cannot estimate bottlenecks downstream of where CAKE is attached.
### 5.11 CAKE observability (`tc -s qdisc show`)
### 5.11 DOCSIS modem headroom
When shaping downstream traffic behind a DOCSIS modem, set the shaper below the modem's actual provisioned downstream rate—usually 85–95% after confirming the real rate with traffic tests. Otherwise, traffic can queue in the modem's downstream buffer (notably on modems with large buffers such as some Puma 6 devices), where LibreQoS and CAKE/fq_codel cannot manage it; latency then rises even though the LibreQoS shaper appears correctly configured. This is the same downstream bottleneck that `autorate-ingress` cannot observe. Keep the queue under CAKE or fq_codel by leaving enough headroom, and adjust the percentage for the modem and service tier rather than treating one value as universal.
### 5.12 CAKE observability (`tc -s qdisc show`)
Useful fields commonly present:
+63 -30
View File
@@ -788,11 +788,10 @@ impl BakeryCommands {
let do_sqm;
if execution_mode == ExecutionMode::Builder {
// Initial tree build: always create HTB + SQM classes for circuits,
// regardless of lazy queue mode. Laziness applies to live updates
// (ExecutionMode::LiveUpdate) and pruning, not the first full build.
// HTB-lazy keeps the hierarchy for every circuit but defers leaf qdiscs
// until activity promotes the circuit through the live-update path.
do_htb = true;
do_sqm = true;
do_sqm = !matches!(config.queues.lazy_queues, Some(LazyQueueMode::Htb));
} else {
// We're in live update mode
match config.queues.lazy_queues.as_ref() {
@@ -1350,10 +1349,8 @@ mod tests {
MQ_CREATED.store(false, std::sync::atomic::Ordering::Relaxed);
}
#[test]
fn add_circuit_qdisc_replace_commands_use_explicit_handles_when_enriched() {
let config = Arc::new(Config::default());
let builder_commands = BakeryCommands::AddCircuit {
fn test_circuit_command() -> BakeryCommands {
BakeryCommands::AddCircuit {
circuit_hash: 42,
circuit_name: None,
site_name: None,
@@ -1371,36 +1368,72 @@ mod tests {
ip_addresses: "192.0.2.42/32".to_string(),
sqm_override: None,
}
.to_commands(&config, ExecutionMode::Builder)
.expect("builder add_circuit should emit commands");
}
#[test]
fn add_circuit_qdisc_replace_commands_use_explicit_handles_when_enriched() {
let config = Arc::new(Config::default());
let circuit = test_circuit_command();
let builder_commands = circuit
.to_commands(&config, ExecutionMode::Builder)
.expect("builder add_circuit should emit commands");
assert_qdisc_add_replace_commands_use_explicit_handles(&builder_commands);
let mut live_cfg = Config::default();
live_cfg.queues.lazy_queues = Some(LazyQueueMode::Full);
let live_config = Arc::new(live_cfg);
let live_commands = BakeryCommands::AddCircuit {
circuit_hash: 42,
circuit_name: None,
site_name: None,
parent_class_id: crate::TcHandle::from_u32(0x10020),
up_parent_class_id: crate::TcHandle::from_u32(0x20020),
class_minor: 0x21,
download_bandwidth_min: 10.0,
upload_bandwidth_min: 10.0,
download_bandwidth_max: 100.0,
upload_bandwidth_max: 100.0,
class_major: 0x1,
up_class_major: 0x2,
down_qdisc_handle: Some(0x9000),
up_qdisc_handle: Some(0x9001),
ip_addresses: "192.0.2.42/32".to_string(),
sqm_override: None,
}
.to_commands(&live_config, ExecutionMode::LiveUpdate)
.expect("live add_circuit should emit commands");
let live_commands = circuit
.to_commands(&live_config, ExecutionMode::LiveUpdate)
.expect("live add_circuit should emit commands");
assert_qdisc_add_replace_commands_use_explicit_handles(&live_commands);
}
#[test]
fn htb_lazy_builder_defers_leaf_qdiscs_until_activation() {
let mut htb_cfg = Config::default();
htb_cfg.queues.lazy_queues = Some(LazyQueueMode::Htb);
let htb_config = Arc::new(htb_cfg);
let circuit = test_circuit_command();
let htb_builder_commands = circuit
.to_commands(&htb_config, ExecutionMode::Builder)
.expect("HTB-lazy builder should emit HTB commands");
assert_eq!(
htb_builder_commands
.iter()
.filter(|cmd| cmd.first().is_some_and(|part| part == "class"))
.count(),
2,
"HTB-lazy builder should retain both direction classes"
);
assert_eq!(
htb_builder_commands
.iter()
.filter(|cmd| cmd.first().is_some_and(|part| part == "qdisc"))
.count(),
0,
"HTB-lazy builder should defer leaf qdiscs"
);
let htb_live_commands = circuit
.to_commands(&htb_config, ExecutionMode::LiveUpdate)
.expect("HTB-lazy activation should emit leaf qdiscs");
assert_eq!(
htb_live_commands
.iter()
.filter(|cmd| cmd.first().is_some_and(|part| part == "class"))
.count(),
0,
"HTB-lazy activation should reuse the retained HTB classes"
);
assert_eq!(
htb_live_commands
.iter()
.filter(|cmd| cmd.first().is_some_and(|part| part == "qdisc"))
.count(),
2,
"HTB-lazy activation should create both direction leaf qdiscs"
);
}
#[test]
fn mq_setup_observe_mode_reuses_retained_root_on_a_stick() {
let _guard = test_state_lock().lock().expect("lock");