Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ pub struct Builder {
///
/// When this gets exceeded, we issue GOAWAYs.
local_max_error_reset_streams: Option<usize>,

/// connection-level budget for DATA framing overhead.
///
/// When this gets exhausted, we issue a GOAWAY with `ENHANCE_YOUR_CALM`.
data_frame_budget: usize,
}

#[derive(Debug)]
Expand Down Expand Up @@ -663,6 +668,7 @@ impl Builder {
settings: Default::default(),
stream_id: 1.into(),
local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX),
data_frame_budget: proto::DEFAULT_DATA_FRAME_BUDGET,
}
}

Expand Down Expand Up @@ -1145,6 +1151,26 @@ impl Builder {
self
}

/// HTTP/2 flow control limits DATA payload bytes, but it does not limit the number of frames carrying those bytes.
/// A peer could fragment data into many tiny frames, causing disproportionate memory usage from queued events and
/// slab entries while remaining within the flow-control windows.
///
/// This set a connection-level budget for DATA framing overhead. Small frames consume budget according
/// to the difference between their payload length and the approximate cost of a buffered event.
/// Larger frames replenish the budget, up to its original limit. When the application consumes a queued small frame,
/// its buffering charge is also returned.
///
/// Non-final DATA frames with an empty decoded payload are discarded after their flow-control accounting is handled.
/// Because they are never exposed to the application, their budget is not returned.
///
/// Exhausting the budget closes the connection with ENHANCE_YOUR_CALM.
///
/// Default 25600 bytes
pub fn data_frame_budget(&mut self, budget: usize) -> &mut Self {
self.data_frame_budget = budget;
self
}

/// Sets the first stream ID to something other than 1.
#[cfg(feature = "unstable")]
pub fn initial_stream_id(&mut self, stream_id: u32) -> &mut Self {
Expand Down Expand Up @@ -1335,6 +1361,7 @@ where
remote_reset_stream_max: builder.pending_accept_reset_stream_max,
local_error_reset_streams_max: builder.local_max_error_reset_streams,
settings: builder.settings,
data_frame_budget: builder.data_frame_budget,
},
);
let send_request = SendRequest {
Expand Down
2 changes: 2 additions & 0 deletions src/proto/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub(crate) struct Config {
pub remote_reset_stream_max: usize,
pub local_error_reset_streams_max: Option<usize>,
pub settings: frame::Settings,
pub data_frame_budget: usize,
}

#[derive(Debug)]
Expand Down Expand Up @@ -123,6 +124,7 @@ where
.max_concurrent_streams()
.map(|max| max as usize),
local_max_error_reset_streams: config.local_error_reset_streams_max,
data_frame_budget: config.data_frame_budget,
}
}
let streams = Streams::new(streams_config(&config));
Expand Down
5 changes: 3 additions & 2 deletions src/proto/streams/counts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub(super) struct Counts {
/// lifetime of the connection.
num_local_error_reset_streams: usize,

/// Credit for receiving DATA frames without excessive framing overhead.
/// connection-level budget for DATA framing overhead.
data_frame_budget: Budget,
}

Expand All @@ -86,7 +86,7 @@ impl Counts {
num_remote_reset_streams: 0,
max_local_error_reset_streams: config.local_max_error_reset_streams,
num_local_error_reset_streams: 0,
data_frame_budget: Budget::new(DEFAULT_DATA_FRAME_BUDGET),
data_frame_budget: Budget::new(config.data_frame_budget),
}
}

Expand Down Expand Up @@ -356,6 +356,7 @@ mod tests {
remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE,
remote_max_initiated: None,
local_max_error_reset_streams: None,
data_frame_budget: DEFAULT_DATA_FRAME_BUDGET,
},
)
}
Expand Down
5 changes: 5 additions & 0 deletions src/proto/streams/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ pub struct Config {
///
/// When this gets exceeded, we issue GOAWAYs.
pub local_max_error_reset_streams: Option<usize>,

/// connection-level budget (in bytes) for DATA framing overhead.
///
/// Default 25600 bytes
pub data_frame_budget: usize,
}

trait DebugStructExt<'a, 'b> {
Expand Down
1 change: 1 addition & 0 deletions src/proto/streams/recv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,7 @@ mod tests {
remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE,
remote_max_initiated: None,
local_max_error_reset_streams: None,
data_frame_budget: DEFAULT_DATA_FRAME_BUDGET,
};
let mut recv = Recv::new(peer::Dyn::Server, &config);
let mut store = Store::new();
Expand Down
28 changes: 27 additions & 1 deletion src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ pub struct Builder {
///
/// When this gets exceeded, we issue GOAWAYs.
local_max_error_reset_streams: Option<usize>,

/// connection-level budget for DATA framing overhead.
///
/// When this gets exhausted, we issue a GOAWAY with `ENHANCE_YOUR_CALM`.
data_frame_budget: usize,
}

/// Send a response back to the client
Expand Down Expand Up @@ -655,8 +660,8 @@ impl Builder {
settings: Settings::default(),
initial_target_connection_window_size: None,
max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE,

local_max_error_reset_streams: Some(proto::DEFAULT_LOCAL_RESET_COUNT_MAX),
data_frame_budget: proto::DEFAULT_DATA_FRAME_BUDGET,
}
}

Expand Down Expand Up @@ -1040,6 +1045,26 @@ impl Builder {
self
}

/// HTTP/2 flow control limits DATA payload bytes, but it does not limit the number of frames carrying those bytes.
/// A peer could fragment data into many tiny frames, causing disproportionate memory usage from queued events and
/// slab entries while remaining within the flow-control windows.
///
/// This set a connection-level budget for DATA framing overhead. Small frames consume budget according
/// to the difference between their payload length and the approximate cost of a buffered event.
/// Larger frames replenish the budget, up to its original limit. When the application consumes a queued small frame,
/// its buffering charge is also returned.
///
/// Non-final DATA frames with an empty decoded payload are discarded after their flow-control accounting is handled.
/// Because they are never exposed to the application, their budget is not returned.
///
/// Exhausting the budget closes the connection with ENHANCE_YOUR_CALM.
///
/// Default 25600 bytes
pub fn data_frame_budget(&mut self, budget: usize) -> &mut Self {
self.data_frame_budget = budget;
self
}

/// Creates a new configured HTTP/2 server backed by `io`.
///
/// It is expected that `io` already be in an appropriate state to commence
Expand Down Expand Up @@ -1505,6 +1530,7 @@ where
.builder
.local_max_error_reset_streams,
settings: self.builder.settings.clone(),
data_frame_budget: self.builder.data_frame_budget,
},
);

Expand Down