Skip to content

Commit 3850acd

Browse files
authored
refactor: split empty data frame count from budget (#945)
1 parent 73f39d4 commit 3850acd

4 files changed

Lines changed: 49 additions & 4 deletions

File tree

src/client.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,8 @@ impl Builder {
11601160
///
11611161
/// Small DATA frames consume this budget. The budget is restored when
11621162
/// buffered frames are consumed by the application, while sufficiently
1163-
/// large frames may also restore budget.
1163+
/// large frames may also restore budget. Empty DATA frames are limited
1164+
/// separately and do not consume this budget.
11641165
///
11651166
/// When this budget is exhausted, the connection is closed with
11661167
/// `ENHANCE_YOUR_CALM`.

src/proto/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub const DEFAULT_LOCAL_RESET_COUNT_MAX: usize = 1024;
3737
// smaller than this consume more internal bookkeeping than useful data.
3838
pub const DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD: usize = 256;
3939
pub const DEFAULT_DATA_FRAME_BUDGET: usize = DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD * 100;
40+
pub const MAX_RECV_EMPTY_DATA_FRAMES: usize = 100;
4041
// RFC 9113 suggests allowing at minimum 100 streams, it seems reasonable to
4142
// by default allow a portion of that to be remembered as reset for some time.
4243
pub const DEFAULT_RESET_STREAM_MAX: usize = 50;

src/proto/streams/counts.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ pub(super) struct Counts {
6969

7070
/// connection-level budget for DATA framing overhead.
7171
data_frame_budget: Budget,
72+
73+
/// Number of empty, non-final DATA frames received over the lifetime of
74+
/// the connection.
75+
num_recv_empty_data_frames: usize,
7276
}
7377

7478
impl Counts {
@@ -87,12 +91,22 @@ impl Counts {
8791
max_local_error_reset_streams: config.local_max_error_reset_streams,
8892
num_local_error_reset_streams: 0,
8993
data_frame_budget: Budget::new(config.data_frame_budget),
94+
num_recv_empty_data_frames: 0,
9095
}
9196
}
9297

9398
/// Records the framing overhead of a DATA frame.
9499
pub fn record_data_frame(&mut self, payload_len: usize) -> Result<(), BudgetExhausted> {
95-
if payload_len < DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD {
100+
if payload_len == 0 {
101+
self.num_recv_empty_data_frames = self
102+
.num_recv_empty_data_frames
103+
.checked_add(1)
104+
.ok_or(BudgetExhausted)?;
105+
if self.num_recv_empty_data_frames > MAX_RECV_EMPTY_DATA_FRAMES {
106+
return Err(BudgetExhausted);
107+
}
108+
Ok(())
109+
} else if payload_len < DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD {
96110
self.data_frame_budget
97111
.consume(DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD - payload_len)
98112
} else {
@@ -105,7 +119,7 @@ impl Counts {
105119
/// Releases the framing overhead of a DATA frame that is no longer
106120
/// buffered internally.
107121
pub fn release_data_frame(&mut self, payload_len: usize) {
108-
if payload_len < DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD {
122+
if payload_len != 0 && payload_len < DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD {
109123
self.data_frame_budget
110124
.replenish(DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD - payload_len);
111125
}
@@ -399,4 +413,32 @@ mod tests {
399413
counts.release_data_frame(1);
400414
}
401415
}
416+
417+
#[test]
418+
fn empty_data_frames_do_not_consume_data_frame_budget() {
419+
let mut counts = counts();
420+
counts.data_frame_budget = Budget::new(0);
421+
422+
for _ in 0..MAX_RECV_EMPTY_DATA_FRAMES {
423+
counts.record_data_frame(0).unwrap();
424+
}
425+
426+
// Empty frames have their own limit, while a non-empty small frame
427+
// still consumes the independently configured DATA frame budget.
428+
assert!(counts.record_data_frame(0).is_err());
429+
assert!(counts.record_data_frame(1).is_err());
430+
}
431+
432+
#[test]
433+
fn large_data_frames_do_not_replenish_empty_data_frame_limit() {
434+
let mut counts = counts();
435+
436+
for _ in 0..MAX_RECV_EMPTY_DATA_FRAMES {
437+
counts.record_data_frame(0).unwrap();
438+
counts
439+
.record_data_frame(DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD * 2)
440+
.unwrap();
441+
}
442+
assert!(counts.record_data_frame(0).is_err());
443+
}
402444
}

src/server.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,8 @@ impl Builder {
10541054
///
10551055
/// Small DATA frames consume this budget. The budget is restored when
10561056
/// buffered frames are consumed by the application, while sufficiently
1057-
/// large frames may also restore budget.
1057+
/// large frames may also restore budget. Empty DATA frames are limited
1058+
/// separately and do not consume this budget.
10581059
///
10591060
/// When this budget is exhausted, the connection is closed with
10601061
/// `ENHANCE_YOUR_CALM`.

0 commit comments

Comments
 (0)