Add max send buffer per stream option (#580)
This commit is contained in:
@@ -320,6 +320,9 @@ pub struct Builder {
|
|||||||
/// Initial target window size for new connections.
|
/// Initial target window size for new connections.
|
||||||
initial_target_connection_window_size: Option<u32>,
|
initial_target_connection_window_size: Option<u32>,
|
||||||
|
|
||||||
|
/// Maximum amount of bytes to "buffer" for writing per stream.
|
||||||
|
max_send_buffer_size: usize,
|
||||||
|
|
||||||
/// Maximum number of locally reset streams to keep at a time.
|
/// Maximum number of locally reset streams to keep at a time.
|
||||||
reset_stream_max: usize,
|
reset_stream_max: usize,
|
||||||
|
|
||||||
@@ -628,6 +631,7 @@ impl Builder {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn new() -> Builder {
|
pub fn new() -> Builder {
|
||||||
Builder {
|
Builder {
|
||||||
|
max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE,
|
||||||
reset_stream_duration: Duration::from_secs(proto::DEFAULT_RESET_STREAM_SECS),
|
reset_stream_duration: Duration::from_secs(proto::DEFAULT_RESET_STREAM_SECS),
|
||||||
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
|
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
|
||||||
initial_target_connection_window_size: None,
|
initial_target_connection_window_size: None,
|
||||||
@@ -962,6 +966,24 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum send buffer size per stream.
|
||||||
|
///
|
||||||
|
/// Once a stream has buffered up to (or over) the maximum, the stream's
|
||||||
|
/// flow control will not "poll" additional capacity. Once bytes for the
|
||||||
|
/// stream have been written to the connection, the send buffer capacity
|
||||||
|
/// will be freed up again.
|
||||||
|
///
|
||||||
|
/// The default is currently ~400MB, but may change.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function panics if `max` is larger than `u32::MAX`.
|
||||||
|
pub fn max_send_buffer_size(&mut self, max: usize) -> &mut Self {
|
||||||
|
assert!(max <= std::u32::MAX as usize);
|
||||||
|
self.max_send_buffer_size = max;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Enables or disables server push promises.
|
/// Enables or disables server push promises.
|
||||||
///
|
///
|
||||||
/// This value is included in the initial SETTINGS handshake. When set, the
|
/// This value is included in the initial SETTINGS handshake. When set, the
|
||||||
@@ -1184,6 +1206,7 @@ where
|
|||||||
proto::Config {
|
proto::Config {
|
||||||
next_stream_id: builder.stream_id,
|
next_stream_id: builder.stream_id,
|
||||||
initial_max_send_streams: builder.initial_max_send_streams,
|
initial_max_send_streams: builder.initial_max_send_streams,
|
||||||
|
max_send_buffer_size: builder.max_send_buffer_size,
|
||||||
reset_stream_duration: builder.reset_stream_duration,
|
reset_stream_duration: builder.reset_stream_duration,
|
||||||
reset_stream_max: builder.reset_stream_max,
|
reset_stream_max: builder.reset_stream_max,
|
||||||
settings: builder.settings.clone(),
|
settings: builder.settings.clone(),
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ struct DynConnection<'a, B: Buf = Bytes> {
|
|||||||
pub(crate) struct Config {
|
pub(crate) struct Config {
|
||||||
pub next_stream_id: StreamId,
|
pub next_stream_id: StreamId,
|
||||||
pub initial_max_send_streams: usize,
|
pub initial_max_send_streams: usize,
|
||||||
|
pub max_send_buffer_size: usize,
|
||||||
pub reset_stream_duration: Duration,
|
pub reset_stream_duration: Duration,
|
||||||
pub reset_stream_max: usize,
|
pub reset_stream_max: usize,
|
||||||
pub settings: frame::Settings,
|
pub settings: frame::Settings,
|
||||||
@@ -108,6 +109,7 @@ where
|
|||||||
.initial_window_size()
|
.initial_window_size()
|
||||||
.unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE),
|
.unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE),
|
||||||
initial_max_send_streams: config.initial_max_send_streams,
|
initial_max_send_streams: config.initial_max_send_streams,
|
||||||
|
local_max_buffer_size: config.max_send_buffer_size,
|
||||||
local_next_stream_id: config.next_stream_id,
|
local_next_stream_id: config.next_stream_id,
|
||||||
local_push_enabled: config.settings.is_push_enabled().unwrap_or(true),
|
local_push_enabled: config.settings.is_push_enabled().unwrap_or(true),
|
||||||
extended_connect_protocol_enabled: config
|
extended_connect_protocol_enabled: config
|
||||||
|
|||||||
@@ -33,3 +33,4 @@ pub type WindowSize = u32;
|
|||||||
pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1;
|
pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1;
|
||||||
pub const DEFAULT_RESET_STREAM_MAX: usize = 10;
|
pub const DEFAULT_RESET_STREAM_MAX: usize = 10;
|
||||||
pub const DEFAULT_RESET_STREAM_SECS: u64 = 30;
|
pub const DEFAULT_RESET_STREAM_SECS: u64 = 30;
|
||||||
|
pub const DEFAULT_MAX_SEND_BUFFER_SIZE: usize = 1024 * 400;
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub struct Config {
|
|||||||
/// MAX_CONCURRENT_STREAMS specified in the frame.
|
/// MAX_CONCURRENT_STREAMS specified in the frame.
|
||||||
pub initial_max_send_streams: usize,
|
pub initial_max_send_streams: usize,
|
||||||
|
|
||||||
|
/// Max amount of DATA bytes to buffer per stream.
|
||||||
|
pub local_max_buffer_size: usize,
|
||||||
|
|
||||||
/// The stream ID to start the next local stream with
|
/// The stream ID to start the next local stream with
|
||||||
pub local_next_stream_id: StreamId,
|
pub local_next_stream_id: StreamId,
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ pub(super) struct Send {
|
|||||||
/// > the identified last stream.
|
/// > the identified last stream.
|
||||||
max_stream_id: StreamId,
|
max_stream_id: StreamId,
|
||||||
|
|
||||||
|
/// The maximum amount of bytes a stream should buffer.
|
||||||
|
max_buffer_size: usize,
|
||||||
|
|
||||||
/// Initial window size of locally initiated streams
|
/// Initial window size of locally initiated streams
|
||||||
init_window_sz: WindowSize,
|
init_window_sz: WindowSize,
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ impl Send {
|
|||||||
pub fn new(config: &Config) -> Self {
|
pub fn new(config: &Config) -> Self {
|
||||||
Send {
|
Send {
|
||||||
init_window_sz: config.remote_init_window_sz,
|
init_window_sz: config.remote_init_window_sz,
|
||||||
|
max_buffer_size: config.local_max_buffer_size,
|
||||||
max_stream_id: StreamId::MAX,
|
max_stream_id: StreamId::MAX,
|
||||||
next_stream_id: Ok(config.local_next_stream_id),
|
next_stream_id: Ok(config.local_next_stream_id),
|
||||||
prioritize: Prioritize::new(config),
|
prioritize: Prioritize::new(config),
|
||||||
@@ -333,14 +337,10 @@ impl Send {
|
|||||||
|
|
||||||
/// Current available stream send capacity
|
/// Current available stream send capacity
|
||||||
pub fn capacity(&self, stream: &mut store::Ptr) -> WindowSize {
|
pub fn capacity(&self, stream: &mut store::Ptr) -> WindowSize {
|
||||||
let available = stream.send_flow.available().as_size();
|
let available = stream.send_flow.available().as_size() as usize;
|
||||||
let buffered = stream.buffered_send_data;
|
let buffered = stream.buffered_send_data;
|
||||||
|
|
||||||
if available as usize <= buffered {
|
available.min(self.max_buffer_size).saturating_sub(buffered) as WindowSize
|
||||||
0
|
|
||||||
} else {
|
|
||||||
available - buffered as WindowSize
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_reset(
|
pub fn poll_reset(
|
||||||
|
|||||||
@@ -245,6 +245,9 @@ pub struct Builder {
|
|||||||
|
|
||||||
/// Initial target window size for new connections.
|
/// Initial target window size for new connections.
|
||||||
initial_target_connection_window_size: Option<u32>,
|
initial_target_connection_window_size: Option<u32>,
|
||||||
|
|
||||||
|
/// Maximum amount of bytes to "buffer" for writing per stream.
|
||||||
|
max_send_buffer_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a response back to the client
|
/// Send a response back to the client
|
||||||
@@ -633,6 +636,7 @@ impl Builder {
|
|||||||
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
|
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
initial_target_connection_window_size: None,
|
initial_target_connection_window_size: None,
|
||||||
|
max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,6 +874,24 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum send buffer size per stream.
|
||||||
|
///
|
||||||
|
/// Once a stream has buffered up to (or over) the maximum, the stream's
|
||||||
|
/// flow control will not "poll" additional capacity. Once bytes for the
|
||||||
|
/// stream have been written to the connection, the send buffer capacity
|
||||||
|
/// will be freed up again.
|
||||||
|
///
|
||||||
|
/// The default is currently ~400MB, but may change.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function panics if `max` is larger than `u32::MAX`.
|
||||||
|
pub fn max_send_buffer_size(&mut self, max: usize) -> &mut Self {
|
||||||
|
assert!(max <= std::u32::MAX as usize);
|
||||||
|
self.max_send_buffer_size = max;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the maximum number of concurrent locally reset streams.
|
/// Sets the maximum number of concurrent locally reset streams.
|
||||||
///
|
///
|
||||||
/// When a stream is explicitly reset by either calling
|
/// When a stream is explicitly reset by either calling
|
||||||
@@ -1290,6 +1312,7 @@ where
|
|||||||
next_stream_id: 2.into(),
|
next_stream_id: 2.into(),
|
||||||
// Server does not need to locally initiate any streams
|
// Server does not need to locally initiate any streams
|
||||||
initial_max_send_streams: 0,
|
initial_max_send_streams: 0,
|
||||||
|
max_send_buffer_size: self.builder.max_send_buffer_size,
|
||||||
reset_stream_duration: self.builder.reset_stream_duration,
|
reset_stream_duration: self.builder.reset_stream_duration,
|
||||||
reset_stream_max: self.builder.reset_stream_max,
|
reset_stream_max: self.builder.reset_stream_max,
|
||||||
settings: self.builder.settings.clone(),
|
settings: self.builder.settings.clone(),
|
||||||
|
|||||||
@@ -1611,3 +1611,60 @@ async fn poll_capacity_after_send_data_and_reserve() {
|
|||||||
|
|
||||||
join(srv, h2).await;
|
join(srv, h2).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn max_send_buffer_size_overflow() {
|
||||||
|
h2_support::trace_init!();
|
||||||
|
let (io, mut srv) = mock::new();
|
||||||
|
|
||||||
|
let srv = async move {
|
||||||
|
let settings = srv.assert_client_handshake().await;
|
||||||
|
assert_default_settings!(settings);
|
||||||
|
srv.recv_frame(frames::headers(1).request("POST", "https://www.example.com/"))
|
||||||
|
.await;
|
||||||
|
srv.send_frame(frames::headers(1).response(200).eos()).await;
|
||||||
|
srv.recv_frame(frames::data(1, &[0; 10][..])).await;
|
||||||
|
srv.recv_frame(frames::data(1, &[][..]).eos()).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = async move {
|
||||||
|
let (mut client, mut conn) = client::Builder::new()
|
||||||
|
.max_send_buffer_size(5)
|
||||||
|
.handshake::<_, Bytes>(io)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let request = Request::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri("https://www.example.com/")
|
||||||
|
.body(())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (response, mut stream) = client.send_request(request, false).unwrap();
|
||||||
|
|
||||||
|
let response = conn.drive(response).await.unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
assert_eq!(stream.capacity(), 0);
|
||||||
|
stream.reserve_capacity(10);
|
||||||
|
assert_eq!(
|
||||||
|
stream.capacity(),
|
||||||
|
5,
|
||||||
|
"polled capacity not over max buffer size"
|
||||||
|
);
|
||||||
|
|
||||||
|
stream.send_data([0; 10][..].into(), false).unwrap();
|
||||||
|
|
||||||
|
stream.reserve_capacity(15);
|
||||||
|
assert_eq!(
|
||||||
|
stream.capacity(),
|
||||||
|
0,
|
||||||
|
"now with buffered over the max, don't overflow"
|
||||||
|
);
|
||||||
|
stream.send_data([0; 0][..].into(), true).unwrap();
|
||||||
|
|
||||||
|
// Wait for the connection to close
|
||||||
|
conn.await.unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
join(srv, client).await;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user