Skip to content

Fix congestion window wrap-around on latency spike - #2185

Merged
CendioOssman merged 1 commit into
TigerVNC:masterfrom
tobfah:congestion-spike-wrap
Nov 6, 2026
Merged

CendioOssman merged 1 commit into
TigerVNC:masterfrom
tobfah:congestion-spike-wrap

Conversation

@tobfah

@tobfah tobfah commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

This PR fixes a potential unsigned wrap-around for the congestion window which has the unintended consequence of setting it to the maximum window value as opposed to keeping it low.

This also avoids double adjustment of the window by making the two branches (latency spike and congestion avoidance) mutually exclusive.

Since da8904c a latency spike (diff > 100) both scales the congestion
window proportionally and always subtracts an additional 4 KiB. This
could have the unintended effect that the unsigned congWindow,
potentially below 4 KiB at the point of the subtraction, wraps around
and gets clamped to MAXIMUM_WINDOW.

This fix avoids the wrap around and makes the spike handling and
congestion avoidance mutually exclusive.
@CendioOssman

Copy link
Copy Markdown
Member

When does this occur? Can it be easily tested?

@CendioOssman

Copy link
Copy Markdown
Member

It looks like your commit email doesn't match what you have registered at GitHub? The commit isn't attributed to you.

@tobfah

tobfah commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

It looks like your commit email doesn't match what you have registered at GitHub? The commit isn't attributed to you.

Looks like the domain used in my local configuration (.se) didn't match my github account (.com). Should be fixed now.

@tobfah

tobfah commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

When does this occur?

It happens when the server detects a latency spike (meeting a certain condition) and the congestion window happens to currently sit at a value such that the proportional adjustment factor baseRTT / minRTT would bring it below 4 KiB.

The ratio above is between the estimated base latency of the network (baseRTT) and the minimum measured latency (minRTT) since the last window adjustment (based on a short series of measurements).

As seen in Congestion::updateCongestion(), if diff = minRTT - baseRTT fulfills the condition diff > 100 && diff > baseRTT/2 we'll apply the adjustment factor baseRTT / minRTT and then this invariably falls through to an additional 4 KiB subtraction from congWindow further down since at that point inSlowStart = false and diff > 50 is implied from the earlier diff > 100.

If the first adjustment sent congWindow below 4 KiB then the subsequent 4 KiB subtraction wraps it around to somewhere close to its unsigned maximum. This is then clamped to the MAXIMUM_WINDOW value of 4096 KiB towards the end.

Can it be easily tested?

I've managed to reproduce this a couple of times by having the server send quite small screen updates (some text fluttering in the corner of a terminal window) on an unthrottled network and then suddenly throttling it to 300ms.

With some added debug logging I could observe for instance the congestion window at 128 KiB on entering Congestion::updateCongestion() with baseRTT at 1 ms and minRTT at ~300 ms. The window was adjusted to roughly 400 bytes via the ~1/300 factor, and subsequently wrapped to a value not far from the unsigned maximum of congWindow via the 4 KiB subtraction, and finally clamped to the MAXIMUM_WINDOW value at the end.

@CendioOssman
CendioOssman merged commit a17d6ff into TigerVNC:master Nov 6, 2026
34 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants