With multipath negotiated and two validated paths, all pending PATH_ACK frames are written into the first packet the transmit loop builds, regardless of which path they acknowledge. In practice that is path 0, so path 1's acknowledgements ride path 0.
That couples path 1's loss recovery and congestion feedback to path 0's condition. When path 0 degrades (a link losing capacity, a queue building behind it), path 1's acks sit in path 0's queue; path 1's RTT and delivery-rate samples inflate with a queue it is not on, and its congestion controller backs off although the path itself is fine.
How I hit it: a paced media source over two links (a link-bonding experiment on a netem rig), with path 0 throttled to 64 kbit mid-stream. Path 1 never ramped; delivery sat around 65 % for the rest of the session. With acks kept on their own path it is 97–98 %.
Repro in the proto test harness: two validated paths, stream data on both. Counting PathStats.frame_tx.path_acks on the server per path gives 6 on path 0 and 0 on path 1.
Proposed fix: send a path's acks on that path whenever it can carry them (known CIDs, not abandoned, validated) and only piggyback them on another path when it cannot. It is a small change in populate_packet and PacketSpace::can_send. I have it working with a regression test and will open a PR referencing this issue.
With multipath negotiated and two validated paths, all pending PATH_ACK frames are written into the first packet the transmit loop builds, regardless of which path they acknowledge. In practice that is path 0, so path 1's acknowledgements ride path 0.
That couples path 1's loss recovery and congestion feedback to path 0's condition. When path 0 degrades (a link losing capacity, a queue building behind it), path 1's acks sit in path 0's queue; path 1's RTT and delivery-rate samples inflate with a queue it is not on, and its congestion controller backs off although the path itself is fine.
How I hit it: a paced media source over two links (a link-bonding experiment on a netem rig), with path 0 throttled to 64 kbit mid-stream. Path 1 never ramped; delivery sat around 65 % for the rest of the session. With acks kept on their own path it is 97–98 %.
Repro in the proto test harness: two validated paths, stream data on both. Counting
PathStats.frame_tx.path_ackson the server per path gives 6 on path 0 and 0 on path 1.Proposed fix: send a path's acks on that path whenever it can carry them (known CIDs, not abandoned, validated) and only piggyback them on another path when it cannot. It is a small change in
populate_packetandPacketSpace::can_send. I have it working with a regression test and will open a PR referencing this issue.