From 8bea1e358da6303a66a23618c8ec61b568080001 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Thu, 30 Jul 2026 20:20:10 -0500 Subject: [PATCH 1/2] Docs: Explain DOCSIS modem headroom --- docs/v2.0/htb_fq_codel_cake.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/v2.0/htb_fq_codel_cake.md b/docs/v2.0/htb_fq_codel_cake.md index 211ee966..335177a8 100644 --- a/docs/v2.0/htb_fq_codel_cake.md +++ b/docs/v2.0/htb_fq_codel_cake.md @@ -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: From b89fe5d33a083f01f2f3b8dbdfa4b5597423f85a Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Fri, 31 Jul 2026 12:54:54 -0500 Subject: [PATCH 2/2] Fix HTB-lazy full reload queue growth --- src/rust/lqos_bakery/src/commands.rs | 93 +++++++++++++++++++--------- 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/src/rust/lqos_bakery/src/commands.rs b/src/rust/lqos_bakery/src/commands.rs index 5e7e623e..5b7b398a 100644 --- a/src/rust/lqos_bakery/src/commands.rs +++ b/src/rust/lqos_bakery/src/commands.rs @@ -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");