Flow 1.0.1
Flow project: Full implementation reference.
cong_ctl_util.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
23#include <boost/weak_ptr.hpp>
24#include <string>
25
26namespace flow::net_flow
27{
28// Types.
29
30/**
31 * Utility class for use by Congestion_control_strategy implementations that implements congestion
32 * window and slow start threshold storage and classic Reno-style "slow start" and "congestion
33 * avoidance" algorithms.
34 *
35 * While all (most?) congestion control algorithms store a CWND value, how each one computes or stores it is
36 * entirely up to the particular implementation. Thus each Congestion_control_strategy subclass can
37 * just store its own CWND data member, possibly an SSTHRESH data member, and re-implement slow
38 * start and congestion avoidance as needed.
39 *
40 * However, many (most?) congestion control algorithms use the classic TCP congestion control (Reno)
41 * slow start and congestion avoidance algorithms. That is, upon acknowledgment, CWND increases
42 * either linearly (if CWND >= SSTRESH) or exponentially (CWND < SSTHRESH); these are "congestion
43 * avoidance" and "slow start" modes respectively. Where most (all?) Reno-based algorithms (e.g.,
44 * Westwood+) differ is what they do to CWND when loss is detected; they may halve SSTHRESH and CWND
45 * or do some other thing. However, in almost all cases, at least in some situations CWND grows
46 * according to either SS or CA based on SSTHRESH. Therefore, for code reuse among the various
47 * Congestion_control_strategy implementations, this class stores CWND and SSTHRESH, increases the
48 * former when asked to (based on CWND vs. SSTHRESH), and sets SSTHRESH and CWND to arbitrary given
49 * values when asked to.
50 *
51 * The Linux TCP implementation is kind of based on the same idea, wherein a Reno-style `struct` storing CWND,
52 * SSTHRESH, and acked-bytes-so-far values also has a pointer to a "polymorphic" `struct` corresponding to the
53 * custom congestion control implementation (e.g., a Westwood-specific `struct` storing its bandwidth estimation
54 * variables). Then when Westwood CC is enabled, its event handlers are set to Reno functions when classic SS
55 * and CA are to be performed and to Westwood functions when a Westwood-specific action is to be performed
56 * (set CWND to the bandwidth estimate * RTT-min). It's hard to put into words, but while the idea is the
57 * same, it's not really executed quite as cleanly as it could be. The Linux-equivalent implementation in our
58 * framework would be to have Congestion_control_classic store CWND and SSTHRESH and implement classic SS/CA
59 * as well as CWND/SSTHRESH halving, then add Congestion_control_classic_with_bandwidth_est deriving from this
60 * and try to alter the superclass's behavior through inheritance (CWND setting to bandwidth-based value).
61 * Instead of forcing this IS-A relationship (as Linux kind of does), it seems cleaner and more flexible to
62 * use a HAS-A relationship: Congestion_control_classic HAS-A Congestion_control_classic_data (and reuses all
63 * of its code, and adds SSTHRESH/CWND halving on loss); Congestion_control_classic_with_bandwidth_est HAS-A
64 * Congestion_control_classic_data (and reuses all of its code, plus adds setting SSTHRESH/CWND to a
65 * bandwidth-based value).
66 *
67 * ### Documentation ###
68 * Because this class explicitly implements Reno-style CWND growth, upon which any user class
69 * must be able to rely exactly, this class is uncharacteristically white-boxy in terms of its API and API
70 * documentation. Each method documents exactly what happens to CWND and SSTHRESH; and no CWND/SSTHRESH
71 * changes are allowed except as documented.
72 *
73 * ### Thread safety ###
74 * Same as Congestion_control_strategy.
75 */
77 public log::Log_context,
78 private boost::noncopyable
79{
80public:
81 // Constructors/destructor.
82
83 /**
84 * Constructs object by setting up logging and saving a pointer to the containing Peer_socket,
85 * setting CWND to a reasonable initial value (also known as Initial Window or IW) per classic TCP RFCs,
86 * and setting SSTHRESH to infinity (ditto).
87 *
88 * @param logger_ptr
89 * The Logger implementation to use subsequently.
90 * @param sock
91 * The Peer_socket.
92 */
94
95 // Methods.
96
97 /**
98 * Return current stored CWND value in bytes. This corresponds to
99 * Congestion_control_strategy::congestion_window_bytes().
100 *
101 * @return Ditto.
102 */
103 size_t congestion_window_bytes() const;
104
105 /**
106 * Return current stored SSTHRESH value in bytes. When congestion_window_bytes() >= this value,
107 * it grows according to congestion avoidance mode (linearly). Otherwise it grows according to
108 * slow start mode (exponentially).
109 *
110 * @return See above. Infinity is `"numeric_limits<size_t>::max()"`.
111 */
112 size_t slow_start_threshold_bytes() const;
113
114 /**
115 * The value to which the constructor set the stored CWND value (also known as Initial Window or IW).
116 * This is provided, because some congestion control algorithms may want to manually set CWND back
117 * to this value on events like Idle Timeout.
118 *
119 * @return See above.
120 */
121 size_t init_congestion_window_bytes() const;
122
123 /**
124 * Adjusts state, including potentially CWND, based on either "congestion avoidance" or "slow
125 * start" algorithm, given that packets with the given amount of data were just converted from
126 * In-flight to Acknowledged (i.e., were newly acknowledged). CWND may change. SSTHRESH will not
127 * change.
128 *
129 * Specifically: If CWND < SSTHRESH, then each byte of acked data causes CWND to increase by that
130 * many bytes. If CWND >= SSTHRESH, then each CWND bytes of acked data causes CWND to be
131 * increased by N * max-block-size bytes (in max-block-size-at-a-time increments), where N
132 * is configurable by socket options (N = 1 is the value mandated by RFC 5681). State is
133 * internally maintained to enable the latter to work. This is inspired by the classic TCP
134 * congestion control combined with Appropriate Byte Counting (ABC).
135 *
136 * @param bytes
137 * Same meaning as for Congestion_Congestion_control_strategy::on_acks().
138 */
139 void on_acks(size_t bytes);
140
141 /**
142 * Sets internally stored SSHTRESH and CWND to the given values; appropriately resets internal
143 * state so that on_acks() CWND accounting will continue to work properly. See details inside the
144 * method if needed, but basically since the value of (CWND < SSTHRESH) may be changed by this
145 * assignment, we may move from congestion avoidance to slow start. If CWND changes at all, then
146 * a new congestion avoidance session begins if one is in progress. On the other hand if neither
147 * is true, then congestion avoidance or slow start will continue undisturbed in the next
148 * on_acks() call.
149 *
150 * @param new_slow_start_thresh_bytes
151 * New value for SSHTRESH. (May be unchanged.)
152 * @param new_cong_wnd_bytes
153 * New value for CWND. (May be unchanged.)
154 */
155 void on_congestion_event(size_t new_slow_start_thresh_bytes, size_t new_cong_wnd_bytes);
156
157 /**
158 * Adjust state, including CWND and SSTHRESH, assuming a Drop Timeout just occurred.
159 * This is typically treated similarly in most (all?) congestion control strategies, so it is
160 * factored out into a method. (The caller can of course choose to perform additional steps
161 * before or after.) Both CWND and SSTHRESH will change.
162 *
163 * Specifically: Sets CWND to a low value, since Drop Timeout indicates a major loss event and
164 * possibly major congestion; sets SSTHRESH to the given value (which is highly dependent on the
165 * caller congestion control strategy). The CWND part is taken from classic TCP congestion
166 * control.
167 *
168 * Logging: logs the before/after states of the change, but caller is encouraged to explain what
169 * it is about to do (e.g., halve SSTHRESH in case of classic Reno).
170 *
171 * @param new_slow_start_thresh_bytes
172 * New value for SSHTRESH. (May be unchanged.)
173 */
174 void on_drop_timeout(size_t new_slow_start_thresh_bytes);
175
176 /**
177 * Adjust state, namely CWND, assuming an Idle Timeout just occurred.
178 * This is typically treated similarly in most (all?) congestion control strategies, so it is
179 * factored out into a method. (The caller can of course choose to perform additional steps
180 * before or after.) CWND will change. SSTHRESH will not change.
181 *
182 * Specifically: Sets CWND to a low value (the initial window), thus in effect restarting the
183 * connection (except SSTHRESH is left alone). The policy is taken from classic TCP
184 * congestion control.
185 *
186 * Logs fully.
187 */
188 void on_idle_timeout();
189
190private:
191
192 // Methods.
193
194 /**
195 * Return `true` if and only if CWND < SSTHRESH.
196 *
197 * @return Ditto.
198 */
199 bool in_slow_start() const;
200
201 /**
202 * Helper that returns `true` if and only if the given CWND < the given SSTHRESH.
203 *
204 * @param cong_wnd_bytes
205 * CWND.
206 * @param slow_start_thresh_bytes
207 * SSTHRESH.
208 * @return See above.
209 */
210 static bool in_slow_start(size_t cong_wnd_bytes, size_t slow_start_thresh_bytes);
211
212 /**
213 * Returns `true` if and only if the stored CWND is >= a certain constant maximum value.
214 *
215 * @return Ditto.
216 */
217 bool congestion_window_at_limit() const;
218
219 /// If congestion_window_at_limit(), sets CWND to the limit value.
221
222 /**
223 * Logs, in a TRACE message, all internal state values.
224 *
225 * @param context
226 * A brief description of where the caller is. Preferably this should be lower-case
227 * letters with dashes separating words. We add this to the log message. Example:
228 * `"ack-before"`.
229 */
230 void log_state(const std::string& context) const;
231
232 /**
233 * Analogous to Congestion_control_strategy::socket().
234 *
235 * @return Ditto.
236 */
238
239 // Constants.
240
241 /**
242 * The constant that limits how much CWND can grow in slow start for a single ack group. In
243 * on_acks(), while in_slow_start(), each byte acknowledged increments CWND by 1 byte. However,
244 * if the total number of such bytes exceeds this constant, CWND only increments by this constant
245 * times max-block-size.
246 */
248
249 // Data.
250
251 /// Same meaning as in Congestion_control_strategy.
252 boost::weak_ptr<Peer_socket::Const_ptr::element_type> m_sock;
253
254 /// Current value of CWND.
256
257 /// Initial value of CWND in constructor.
259
260 /// Current value of SSTRESH.
262
263 /**
264 * While in congestion avoidance mode (`!in_slow_start()`), the # of acked bytes reported to
265 * on_acks() that have not yet been accounted for in a CWND increase. This corresponds to the
266 * `bytes_acked` variable in TCP RFC 3465-2.1. When in_slow_start(), this value is zero and unused.
267 *
268 * More formally, the congestion avoidance (CA) algorithm with Appropriate Byte Counting (ABC), as
269 * implemented here, works as follows. When CA is entered, #m_acked_bytes_since_window_update = 0.
270 * Then, in every on_acks() call, it is incremented by `bytes`. Once it exceeds CWND, CWND bytes
271 * are "converted" into a CWND increase of N * max-block-size bytes, by performing said CWND increment
272 * and subtracting the pre-increment CWND value from #m_acked_bytes_since_window_update. The
273 * remainder of this operation is less than CWND and is in #m_acked_bytes_since_window_update. The
274 * latter is then incremented in the next on_acks(), and so on, until CA ends (something causes
275 * in_slow_start()).
276 */
278}; // class Congestion_control_classic_data
279
280} // namespace flow::net_flow
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1619
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
Utility class for use by Congestion_control_strategy implementations that implements congestion windo...
void log_state(const std::string &context) const
Logs, in a TRACE message, all internal state values.
size_t slow_start_threshold_bytes() const
Return current stored SSTHRESH value in bytes.
size_t m_acked_bytes_since_window_update
While in congestion avoidance mode (!in_slow_start()), the # of acked bytes reported to on_acks() tha...
size_t m_slow_start_thresh_bytes
Current value of SSTRESH.
Congestion_control_classic_data(log::Logger *logger_ptr, Peer_socket::Const_ptr sock)
Constructs object by setting up logging and saving a pointer to the containing Peer_socket,...
void on_drop_timeout(size_t new_slow_start_thresh_bytes)
Adjust state, including CWND and SSTHRESH, assuming a Drop Timeout just occurred.
void on_congestion_event(size_t new_slow_start_thresh_bytes, size_t new_cong_wnd_bytes)
Sets internally stored SSHTRESH and CWND to the given values; appropriately resets internal state so ...
void clamp_congestion_window()
If congestion_window_at_limit(), sets CWND to the limit value.
const size_t m_init_cong_wnd_bytes
Initial value of CWND in constructor.
Peer_socket::Const_ptr socket() const
Analogous to Congestion_control_strategy::socket().
bool congestion_window_at_limit() const
Returns true if and only if the stored CWND is >= a certain constant maximum value.
void on_acks(size_t bytes)
Adjusts state, including potentially CWND, based on either "congestion avoidance" or "slow start" alg...
size_t init_congestion_window_bytes() const
The value to which the constructor set the stored CWND value (also known as Initial Window or IW).
static const size_t S_MAX_SLOW_START_CWND_INCREMENT_BLOCKS
The constant that limits how much CWND can grow in slow start for a single ack group.
size_t congestion_window_bytes() const
Return current stored CWND value in bytes.
boost::weak_ptr< Peer_socket::Const_ptr::element_type > m_sock
Same meaning as in Congestion_control_strategy.
bool in_slow_start() const
Return true if and only if CWND < SSTHRESH.
void on_idle_timeout()
Adjust state, namely CWND, assuming an Idle Timeout just occurred.
Const_target_ptr Const_ptr
Short-hand for ref-counted pointer to immutable values of type Target_type::element_type (a-la T cons...
Flow module containing the API and implementation of the Flow network protocol, a TCP-inspired stream...
Definition: node.cpp:25