cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
http2_flow_control.c
Go to the documentation of this file.
2
3#include <limits.h>
4#include <string.h>
5#include "h2_wire.h"
6
15static uint32_t cwist_http2_window_clamp(uint64_t value, uint32_t minimum, uint32_t maximum) {
16 if (value < minimum) {
17 return minimum;
18 }
19 if (value > maximum) {
20 return maximum;
21 }
22 return (uint32_t)value;
23}
24
33static uint64_t cwist_http2_abs_diff(uint64_t left, uint64_t right) {
34 return left >= right ? left - right : right - left;
35}
36
50static void cwist_http2_flow_control_adjust(cwist_http2_flow_control *flow_control) {
54
55 if (!flow_control->rtt_initialized) {
56 return;
57 }
58
59 delay_us = flow_control->srtt_us + (4U * flow_control->rttvar_us);
60 if (flow_control->rttvar_us > flow_control->srtt_us / 2U) {
62 } else if (delay_us >= 200000U) {
64 } else {
66 }
67
68 multiplier = delay_us / 200000U;
69 if (multiplier < 1U) {
70 multiplier = 1U;
71 }
72 if (flow_control->network_quality != CWIST_HTTP2_NETWORK_QUALITY_NORMAL) {
73 ++multiplier;
74 }
75
76 flow_control->target_window =
77 cwist_http2_window_clamp((uint64_t)flow_control->min_window * multiplier,
78 flow_control->min_window, flow_control->max_window);
79 flow_control->pacing_rate_bytes_per_sec = ((uint64_t)flow_control->target_window * 1000000U) /
80 (flow_control->srtt_us ? flow_control->srtt_us : 1U);
81 /* Floor: a single spiked RTT sample must not throttle the connection to
82 * a crawl. Overrating is harmless — the peer's flow-control windows
83 * still gate in-flight bytes; pacing only smooths bursts. */
84 rate_floor = (uint64_t)flow_control->min_window * 10U;
85 if (flow_control->pacing_rate_bytes_per_sec < rate_floor) {
86 flow_control->pacing_rate_bytes_per_sec = rate_floor;
87 }
88}
89
100static int cwist_http2_write_window_update(uint8_t *buffer, size_t buffer_len, size_t *written,
101 uint32_t stream_id, uint32_t increment) {
102 if (written != NULL) {
103 *written = 0;
104 }
105 /* A WINDOW_UPDATE frame is 9 bytes of header + 4 bytes of payload;
106 * accepting buffer_len < 13 used to write 4 bytes past the caller's
107 * buffer. */
108 if (buffer == NULL || written == NULL || buffer_len < 13U || increment == 0U) {
109 return -1;
110 }
111
112 h2_put_u24(buffer, 4);
113 buffer[3] = 0x08; /* WINDOW_UPDATE */
114 buffer[4] = 0;
115 h2_put_u31(buffer + 5, stream_id);
116 h2_put_u32(buffer + 9, increment);
117 *written = 13U;
118 return 0;
119}
120
140 if (flow_control == NULL) {
141 return;
142 }
143 /* Anything below the default minimum means "use the default". */
146 }
149 }
152 }
153
154 memset(flow_control, 0, sizeof(*flow_control));
155 flow_control->receive_window = initial_connection_window;
161 /* Start with half a window of burst credit; the bucket refills at
162 * pacing_rate once RTT samples calibrate it. */
163 flow_control->pacing_tokens = initial_connection_window / 2U;
164 flow_control->pacing_rate_bytes_per_sec = initial_connection_window * 10U;
165}
166
206
222
223 if (flow_control == NULL || rtt_sample_us == 0U) {
224 return;
225 }
226 if (!flow_control->rtt_initialized) {
227 flow_control->srtt_us = rtt_sample_us;
228 flow_control->rttvar_us = rtt_sample_us / 2U;
229 flow_control->rtt_initialized = true;
230 } else {
231 difference = cwist_http2_abs_diff(flow_control->srtt_us, rtt_sample_us);
232 flow_control->rttvar_us = (3U * flow_control->rttvar_us + difference) / 4U;
233 flow_control->srtt_us = (7U * flow_control->srtt_us + rtt_sample_us) / 8U;
234 }
235 cwist_http2_flow_control_adjust(flow_control);
236}
237
247 if (flow_control == NULL || bytes > flow_control->receive_window) {
248 return false;
249 }
250 flow_control->receive_window -= bytes;
251 return true;
252}
253
263 uint32_t bytes) {
264 if (flow_control == NULL || bytes > flow_control->receive_window) {
265 return false;
266 }
267 flow_control->receive_window -= bytes;
268 return true;
269}
270
281 if (flow_control != NULL) {
282 flow_control->pending_update += bytes > UINT32_MAX - flow_control->pending_update
283 ? UINT32_MAX - flow_control->pending_update
284 : bytes;
285 }
286}
287
298 uint32_t bytes) {
299 if (flow_control != NULL) {
300 flow_control->pending_update += bytes > UINT32_MAX - flow_control->pending_update
301 ? UINT32_MAX - flow_control->pending_update
302 : bytes;
303 }
304}
305
324static uint32_t cwist_http2_flow_control_retune_target(uint32_t pending_update, uint64_t srtt_us,
332
333 if (interval_us == 0) {
334 return current_target;
335 }
336 bdp2 = (2ULL * (uint64_t)pending_update * srtt_us) / interval_us;
337 target = cwist_http2_window_clamp(bdp2, minimum, maximum);
340 if ((uint64_t)target > grow_cap) {
341 target = cwist_http2_window_clamp(grow_cap, minimum, maximum);
342 } else if ((uint64_t)target < shrink_floor) {
343 target = cwist_http2_window_clamp(shrink_floor, minimum, maximum);
344 }
345 return target;
346}
347
359static uint32_t cwist_http2_flow_control_increment(uint32_t pending_update, uint32_t receive_window,
360 uint32_t target_window) {
361 uint32_t top_up = receive_window < target_window ? target_window - receive_window : 0U;
362 return pending_update > top_up ? pending_update : top_up;
363}
364
386 uint8_t *buffer, size_t buffer_len,
387 size_t *written, uint64_t now_us, bool force) {
389
390 if (written != NULL) {
391 *written = 0;
392 }
393 if (flow_control == NULL || flow_control->pending_update == 0U ||
394 (!force && flow_control->pending_update < flow_control->target_window / 2U)) {
395 return 0;
396 }
397
398 if (flow_control->rtt_initialized && flow_control->last_window_update_us != 0U &&
399 now_us > flow_control->last_window_update_us) {
400 flow_control->target_window = cwist_http2_flow_control_retune_target(
401 flow_control->pending_update, flow_control->srtt_us,
402 now_us - flow_control->last_window_update_us, flow_control->target_window,
403 flow_control->min_window, flow_control->max_window);
404 }
405
406 increment = cwist_http2_flow_control_increment(
407 flow_control->pending_update, flow_control->receive_window, flow_control->target_window);
408 if (cwist_http2_write_window_update(buffer, buffer_len, written, 0U, increment) != 0) {
409 return -1;
410 }
411 flow_control->receive_window = increment > CWIST_HTTP2_MAX_WINDOW - flow_control->receive_window
413 : flow_control->receive_window + increment;
414 flow_control->pending_update = 0;
415 flow_control->last_window_update_us = now_us;
416 return 1;
417}
418
446 size_t *written, uint64_t now_us, bool force) {
448
449 if (written != NULL) {
450 *written = 0;
451 }
453 return -1;
454 }
455 /* Server-side only client-initiated (odd, non-zero) streams may receive
456 * a stream-level WINDOW_UPDATE. */
457 if (stream_flow_control->stream_id == 0U || (stream_flow_control->stream_id & 1U) == 0U) {
458 return 0;
459 }
460 if (stream_flow_control->pending_update == 0U ||
461 (!force && stream_flow_control->pending_update < stream_flow_control->target_window / 2U)) {
462 return 0;
463 }
464
465 if (connection_flow_control->rtt_initialized &&
466 connection_flow_control->last_window_update_us != 0U &&
467 now_us > connection_flow_control->last_window_update_us) {
468 stream_flow_control->target_window = cwist_http2_flow_control_retune_target(
469 stream_flow_control->pending_update, connection_flow_control->srtt_us,
470 now_us - connection_flow_control->last_window_update_us,
471 stream_flow_control->target_window, stream_flow_control->min_window,
472 stream_flow_control->max_window);
473 }
474
475 increment = cwist_http2_flow_control_increment(stream_flow_control->pending_update,
476 stream_flow_control->receive_window,
477 stream_flow_control->target_window);
478 if (cwist_http2_write_window_update(buffer, buffer_len, written, stream_flow_control->stream_id,
479 increment) != 0) {
480 return -1;
481 }
482 stream_flow_control->receive_window =
485 : stream_flow_control->receive_window + increment;
486 stream_flow_control->pending_update = 0;
487 stream_flow_control->last_window_update_us = now_us;
488 return 1;
489}
490
500 if (flow_control != NULL) {
501 /* Saturating add: credit is capped at the protocol maximum. */
502 flow_control->send_window = increment > CWIST_HTTP2_MAX_WINDOW - flow_control->send_window
504 : flow_control->send_window + increment;
505 }
506}
507
523
547 size_t allowed;
548
550 return 0;
551 }
552 if (connection_flow_control->pacing_last_us == 0U) {
553 connection_flow_control->pacing_last_us = now_us;
554 } else if (now_us > connection_flow_control->pacing_last_us) {
555 elapsed = now_us - connection_flow_control->pacing_last_us;
556 /* The bucket tops out at target_window, so accruing beyond one
557 * window's worth of idle time buys nothing. Capping elapsed at the
558 * exact fill time keeps the multiplication inside uint64 no matter
559 * how long the connection idled or how large the rate is. */
560 fill_us = ((uint64_t)connection_flow_control->target_window * 1000000U) /
561 (connection_flow_control->pacing_rate_bytes_per_sec
562 ? connection_flow_control->pacing_rate_bytes_per_sec
563 : 1U) +
564 1U;
565 if (elapsed > fill_us) {
567 }
568 added = (elapsed * connection_flow_control->pacing_rate_bytes_per_sec) / 1000000U;
569 connection_flow_control->pacing_tokens += added;
570 if (connection_flow_control->pacing_tokens > connection_flow_control->target_window) {
571 connection_flow_control->pacing_tokens = connection_flow_control->target_window;
572 }
573 connection_flow_control->pacing_last_us = now_us;
574 }
575
577 if (allowed > connection_flow_control->send_window) {
578 allowed = connection_flow_control->send_window;
579 }
580 if (allowed > stream_flow_control->send_window) {
581 allowed = stream_flow_control->send_window;
582 }
583 if (allowed > connection_flow_control->pacing_tokens) {
584 allowed = (size_t)connection_flow_control->pacing_tokens;
585 }
586 return allowed;
587}
588
HTTP/2 wire integers in network byte order (private).
void cwist_http2_stream_flow_control_add_send_window(cwist_http2_stream_flow_control *flow_control, uint32_t increment)
Credit a stream send window from a WINDOW_UPDATE frame.
int cwist_http2_flow_control_maybe_window_update(cwist_http2_flow_control *flow_control, uint8_t *buffer, size_t buffer_len, size_t *written, uint64_t now_us, bool force)
Emit a connection-level WINDOW_UPDATE when enough credit is due.
void cwist_http2_flow_control_add_send_window(cwist_http2_flow_control *flow_control, uint32_t increment)
Credit the connection send window from a WINDOW_UPDATE frame.
void cwist_http2_stream_flow_control_consume(cwist_http2_stream_flow_control *flow_control, uint32_t bytes)
Record application-consumed bytes for a stream.
bool cwist_http2_stream_flow_control_receive(cwist_http2_stream_flow_control *flow_control, uint32_t bytes)
Account received DATA bytes against a stream receive window.
void cwist_http2_flow_control_update_rtt(cwist_http2_flow_control *flow_control, uint64_t rtt_sample_us)
Feed a new RTT sample into the smoothed estimator and retune.
void cwist_http2_flow_control_consume(cwist_http2_flow_control *flow_control, uint32_t bytes)
Record application-consumed bytes for the connection.
bool cwist_http2_flow_control_reserve_send(cwist_http2_flow_control *connection_flow_control, cwist_http2_stream_flow_control *stream_flow_control, uint32_t bytes, uint64_t now_us)
Reserve send credit for an outgoing write.
bool cwist_http2_flow_control_receive(cwist_http2_flow_control *flow_control, uint32_t bytes)
Account received DATA bytes against the connection receive window.
size_t cwist_http2_flow_control_pacing_allowance(cwist_http2_flow_control *connection_flow_control, const cwist_http2_stream_flow_control *stream_flow_control, size_t requested, uint64_t now_us)
Compute how much of a send request may proceed right now.
int cwist_http2_stream_flow_control_maybe_window_update(cwist_http2_flow_control *connection_flow_control, cwist_http2_stream_flow_control *stream_flow_control, uint8_t *buffer, size_t buffer_len, size_t *written, uint64_t now_us, bool force)
Emit a stream-level WINDOW_UPDATE when enough credit is due.
void cwist_http2_stream_flow_control_init(cwist_http2_stream_flow_control *flow_control, uint32_t stream_id, uint32_t initial_stream_window, uint32_t max_stream_window)
Initialize per-stream flow-control state.
void cwist_http2_flow_control_init(cwist_http2_flow_control *flow_control, uint32_t initial_connection_window, uint32_t max_connection_window)
Initialize connection-level flow-control state.
#define CWIST_HTTP2_INITIAL_STREAM_WINDOW
#define CWIST_HTTP2_MAX_WINDOW
#define CWIST_HTTP2_INITIAL_CONNECTION_WINDOW
@ CWIST_HTTP2_NETWORK_QUALITY_NORMAL
@ CWIST_HTTP2_NETWORK_QUALITY_HIGH_RTT
@ CWIST_HTTP2_NETWORK_QUALITY_UNSTABLE
size_t cap
Definition otel.c:56