blob: c4c4d96d9edb9939f12909ea2d34d7d8fdc0e30c [file] [log] [blame]
Pierre Ossmanc09e5582015-12-11 20:23:17 +01001/* Copyright 2009-2015 Pierre Ossman for Cendio AB
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19/*
20 * This code implements congestion control in the same way as TCP in
21 * order to avoid excessive latency in the transport. This is needed
22 * because "buffer bloat" is unfortunately still a very real problem.
23 *
24 * The basic principle is TCP Congestion Control (RFC 5618), with the
25 * addition of using the TCP Vegas algorithm. The reason we use Vegas
26 * is that we run on top of a reliable transport so we need a latency
27 * based algorithm rather than a loss based one.
28 */
29
30#include <sys/time.h>
31
32#include <rfb/Congestion.h>
33#include <rfb/util.h>
34
35// Debug output on what the congestion control is up to
36#undef CONGESTION_DEBUG
37
38using namespace rfb;
39
40// This window should get us going fairly fast on a decent bandwidth network.
41// If it's too high, it will rapidly be reduced and stay low.
42static const unsigned INITIAL_WINDOW = 16384;
43
44// TCP's minimal window is 3*MSS. But since we don't know the MSS, we
45// make a guess at 4 KiB (it's probably a bit higher).
46static const unsigned MINIMUM_WINDOW = 4096;
47
48// The current default maximum window for Linux (4 MiB). Should be a good
49// limit for now...
50static const unsigned MAXIMUM_WINDOW = 4194304;
51
52struct Congestion::RTTInfo {
53 struct timeval tv;
54 int offset;
55 unsigned inFlight;
56};
57
58Congestion::Congestion() :
59 baseRTT(-1), congWindow(INITIAL_WINDOW),
60 ackedOffset(0), sentOffset(0),
61 minRTT(-1), seenCongestion(false),
62 congestionTimer(this)
63{
64}
65
66Congestion::~Congestion()
67{
68}
69
70
71void Congestion::sentPing(int offset)
72{
73 struct RTTInfo rttInfo;
74
75 if (ackedOffset == 0)
76 ackedOffset = offset;
77
78 memset(&rttInfo, 0, sizeof(struct RTTInfo));
79
80 gettimeofday(&rttInfo.tv, NULL);
81 rttInfo.offset = offset;
82 rttInfo.inFlight = rttInfo.offset - ackedOffset;
83
84 pings.push_back(rttInfo);
85
86 sentOffset = offset;
87
88 // Let some data flow before we adjust the settings
89 if (!congestionTimer.isStarted())
90 congestionTimer.start(__rfbmin(baseRTT * 2, 100));
91}
92
93void Congestion::gotPong()
94{
95 struct RTTInfo rttInfo;
96 unsigned rtt, delay;
97
98 if (pings.empty())
99 return;
100
101 rttInfo = pings.front();
102 pings.pop_front();
103
104 rtt = msSince(&rttInfo.tv);
105 if (rtt < 1)
106 rtt = 1;
107
108 ackedOffset = rttInfo.offset;
109
110 // Try to estimate wire latency by tracking lowest seen latency
111 if (rtt < baseRTT)
112 baseRTT = rtt;
113
114 if (rttInfo.inFlight > congWindow) {
115 seenCongestion = true;
116
117 // Estimate added delay because of overtaxed buffers
118 delay = (rttInfo.inFlight - congWindow) * baseRTT / congWindow;
119
120 if (delay < rtt)
121 rtt -= delay;
122 else
123 rtt = 1;
124
125 // If we underestimate the congestion window, then we'll get a latency
126 // that's less than the wire latency, which will confuse other portions
127 // of the code.
128 if (rtt < baseRTT)
129 rtt = baseRTT;
130 }
131
132 // We only keep track of the minimum latency seen (for a given interval)
133 // on the basis that we want to avoid continuous buffer issue, but don't
134 // mind (or even approve of) bursts.
135 if (rtt < minRTT)
136 minRTT = rtt;
137}
138
139bool Congestion::isCongested(int offset, unsigned idleTime)
140{
141 // Idle for too long? (and no data on the wire)
142 //
143 // FIXME: This should really just be one baseRTT, but we're getting
144 // problems with triggering the idle timeout on each update.
145 // Maybe we need to use a moving average for the wire latency
146 // instead of baseRTT.
147 if ((sentOffset == ackedOffset) && (idleTime > 2 * baseRTT)) {
148
149#ifdef CONGESTION_DEBUG
150 if (congWindow > INITIAL_WINDOW)
151 fprintf(stderr, "Reverting to initial window (%d KiB) after %d ms\n",
152 INITIAL_WINDOW / 1024, sock->outStream().getIdleTime());
153#endif
154
155 // Close congestion window and allow a transfer
156 // FIXME: Reset baseRTT like Linux Vegas?
157 congWindow = __rfbmin(INITIAL_WINDOW, congWindow);
158
159 return false;
160 }
161
162 // FIXME: Should we compensate for non-update data?
163 // (i.e. use sentOffset instead of offset)
164 if ((offset - ackedOffset) < congWindow)
165 return false;
166
167 // If we just have one outstanding "ping", that means the client has
168 // started receiving our update. In order to not regress compared to
169 // before we had congestion avoidance, we allow another update here.
170 // This could further clog up the tubes, but congestion control isn't
171 // really working properly right now anyway as the wire would otherwise
172 // be idle for at least RTT/2.
173 if (pings.size() == 1)
174 return false;
175
176 return true;
177}
178
179bool Congestion::handleTimeout(Timer* t)
180{
181 unsigned diff;
182
183 if (!seenCongestion)
184 return false;
185
186 // The goal is to have a slightly too large congestion window since
187 // a "perfect" one cannot be distinguished from a too small one. This
188 // translates to a goal of a few extra milliseconds of delay.
189
190 diff = minRTT - baseRTT;
191
192 if (diff > __rfbmin(100, baseRTT)) {
193 // Way too fast
194 congWindow = congWindow * baseRTT / minRTT;
195 } else if (diff > __rfbmin(50, baseRTT/2)) {
196 // Slightly too fast
197 congWindow -= 4096;
198 } else if (diff < 5) {
199 // Way too slow
200 congWindow += 8192;
201 } else if (diff < 25) {
202 // Too slow
203 congWindow += 4096;
204 }
205
206 if (congWindow < MINIMUM_WINDOW)
207 congWindow = MINIMUM_WINDOW;
208 if (congWindow > MAXIMUM_WINDOW)
209 congWindow = MAXIMUM_WINDOW;
210
211#ifdef CONGESTION_DEBUG
212 fprintf(stderr, "RTT: %d ms (%d ms), Window: %d KiB, Bandwidth: %g Mbps\n",
213 minRTT, baseRTT, congWindow / 1024,
214 congWindow * 8.0 / baseRTT / 1000.0);
215#endif
216
217 minRTT = -1;
218 seenCongestion = false;
219
220 return false;
221}
222