Skip to content

Commit d3f37e9

Browse files
committed
feat: add max_pending_accept_reset_streams(n) options
The new option is available to both client and server `Builder`s.
1 parent 5bc8e72 commit d3f37e9

File tree

5 files changed

+104
-4
lines changed

5 files changed

+104
-4
lines changed

src/client.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,10 @@ pub struct Builder {
326326
/// Maximum number of locally reset streams to keep at a time.
327327
reset_stream_max: usize,
328328

329+
/// Maximum number of remotely reset streams to allow in the pending
330+
/// accept queue.
331+
pending_accept_reset_stream_max: usize,
332+
329333
/// Initial `Settings` frame to send as part of the handshake.
330334
settings: Settings,
331335

@@ -634,6 +638,7 @@ impl Builder {
634638
max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE,
635639
reset_stream_duration: Duration::from_secs(proto::DEFAULT_RESET_STREAM_SECS),
636640
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
641+
pending_accept_reset_stream_max: proto::DEFAULT_REMOTE_RESET_STREAM_MAX,
637642
initial_target_connection_window_size: None,
638643
initial_max_send_streams: usize::MAX,
639644
settings: Default::default(),
@@ -966,6 +971,49 @@ impl Builder {
966971
self
967972
}
968973

974+
/// Sets the maximum number of pending-accept remotely-reset streams.
975+
///
976+
/// Streams that have been received by the peer, but not accepted by the
977+
/// user, can also receive a RST_STREAM. This is a legitimate pattern: one
978+
/// could send a request and then shortly after, realize it is not needed,
979+
/// sending a CANCEL.
980+
///
981+
/// However, since those streams are now "closed", they don't count towards
982+
/// the max concurrent streams. So, they will sit in the accept queue,
983+
/// using memory.
984+
///
985+
/// When the number of remotely-reset streams sitting in the pending-accept
986+
/// queue reaches this maximum value, a connection error with the code of
987+
/// `ENHANCE_YOUR_CALM` will be sent to the peer, and returned by the
988+
/// `Future`.
989+
///
990+
/// The default value is currently 20, but could change.
991+
///
992+
/// # Examples
993+
///
994+
/// ```
995+
/// # use tokio::io::{AsyncRead, AsyncWrite};
996+
/// # use h2::client::*;
997+
/// # use bytes::Bytes;
998+
/// #
999+
/// # async fn doc<T: AsyncRead + AsyncWrite + Unpin>(my_io: T)
1000+
/// # -> Result<((SendRequest<Bytes>, Connection<T, Bytes>)), h2::Error>
1001+
/// # {
1002+
/// // `client_fut` is a future representing the completion of the HTTP/2
1003+
/// // handshake.
1004+
/// let client_fut = Builder::new()
1005+
/// .max_pending_accept_reset_streams(100)
1006+
/// .handshake(my_io);
1007+
/// # client_fut.await
1008+
/// # }
1009+
/// #
1010+
/// # pub fn main() {}
1011+
/// ```
1012+
pub fn max_pending_accept_reset_streams(&mut self, max: usize) -> &mut Self {
1013+
self.pending_accept_reset_stream_max = max;
1014+
self
1015+
}
1016+
9691017
/// Sets the maximum send buffer size per stream.
9701018
///
9711019
/// Once a stream has buffered up to (or over) the maximum, the stream's
@@ -1209,6 +1257,7 @@ where
12091257
max_send_buffer_size: builder.max_send_buffer_size,
12101258
reset_stream_duration: builder.reset_stream_duration,
12111259
reset_stream_max: builder.reset_stream_max,
1260+
remote_reset_stream_max: builder.pending_accept_reset_stream_max,
12121261
settings: builder.settings.clone(),
12131262
},
12141263
);

src/proto/connection.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ use std::task::{Context, Poll};
1414
use std::time::Duration;
1515
use tokio::io::{AsyncRead, AsyncWrite};
1616

17-
const DEFAULT_MAX_REMOTE_RESET_STREAMS: usize = 20;
18-
1917
/// An H2 connection
2018
#[derive(Debug)]
2119
pub(crate) struct Connection<T, P, B: Buf = Bytes>
@@ -82,6 +80,7 @@ pub(crate) struct Config {
8280
pub max_send_buffer_size: usize,
8381
pub reset_stream_duration: Duration,
8482
pub reset_stream_max: usize,
83+
pub remote_reset_stream_max: usize,
8584
pub settings: frame::Settings,
8685
}
8786

@@ -120,7 +119,7 @@ where
120119
.unwrap_or(false),
121120
local_reset_duration: config.reset_stream_duration,
122121
local_reset_max: config.reset_stream_max,
123-
remote_reset_max: DEFAULT_MAX_REMOTE_RESET_STREAMS,
122+
remote_reset_max: config.remote_reset_stream_max,
124123
remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE,
125124
remote_max_initiated: config
126125
.settings

src/proto/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub type WindowSize = u32;
3131

3232
// Constants
3333
pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1;
34+
pub const DEFAULT_REMOTE_RESET_STREAM_MAX: usize = 20;
3435
pub const DEFAULT_RESET_STREAM_MAX: usize = 10;
3536
pub const DEFAULT_RESET_STREAM_SECS: u64 = 30;
3637
pub const DEFAULT_MAX_SEND_BUFFER_SIZE: usize = 1024 * 400;

src/server.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ pub struct Builder {
240240
/// Maximum number of locally reset streams to keep at a time.
241241
reset_stream_max: usize,
242242

243+
/// Maximum number of remotely reset streams to allow in the pending
244+
/// accept queue.
245+
pending_accept_reset_stream_max: usize,
246+
243247
/// Initial `Settings` frame to send as part of the handshake.
244248
settings: Settings,
245249

@@ -642,6 +646,7 @@ impl Builder {
642646
Builder {
643647
reset_stream_duration: Duration::from_secs(proto::DEFAULT_RESET_STREAM_SECS),
644648
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
649+
pending_accept_reset_stream_max: proto::DEFAULT_REMOTE_RESET_STREAM_MAX,
645650
settings: Settings::default(),
646651
initial_target_connection_window_size: None,
647652
max_send_buffer_size: proto::DEFAULT_MAX_SEND_BUFFER_SIZE,
@@ -882,6 +887,49 @@ impl Builder {
882887
self
883888
}
884889

890+
/// Sets the maximum number of pending-accept remotely-reset streams.
891+
///
892+
/// Streams that have been received by the peer, but not accepted by the
893+
/// user, can also receive a RST_STREAM. This is a legitimate pattern: one
894+
/// could send a request and then shortly after, realize it is not needed,
895+
/// sending a CANCEL.
896+
///
897+
/// However, since those streams are now "closed", they don't count towards
898+
/// the max concurrent streams. So, they will sit in the accept queue,
899+
/// using memory.
900+
///
901+
/// When the number of remotely-reset streams sitting in the pending-accept
902+
/// queue reaches this maximum value, a connection error with the code of
903+
/// `ENHANCE_YOUR_CALM` will be sent to the peer, and returned by the
904+
/// `Future`.
905+
///
906+
/// The default value is currently 20, but could change.
907+
///
908+
/// # Examples
909+
///
910+
///
911+
/// ```
912+
/// # use tokio::io::{AsyncRead, AsyncWrite};
913+
/// # use h2::server::*;
914+
/// #
915+
/// # fn doc<T: AsyncRead + AsyncWrite + Unpin>(my_io: T)
916+
/// # -> Handshake<T>
917+
/// # {
918+
/// // `server_fut` is a future representing the completion of the HTTP/2
919+
/// // handshake.
920+
/// let server_fut = Builder::new()
921+
/// .max_pending_accept_reset_streams(100)
922+
/// .handshake(my_io);
923+
/// # server_fut
924+
/// # }
925+
/// #
926+
/// # pub fn main() {}
927+
/// ```
928+
pub fn max_pending_accept_reset_streams(&mut self, max: usize) -> &mut Self {
929+
self.pending_accept_reset_stream_max = max;
930+
self
931+
}
932+
885933
/// Sets the maximum send buffer size per stream.
886934
///
887935
/// Once a stream has buffered up to (or over) the maximum, the stream's
@@ -1312,6 +1360,7 @@ where
13121360
max_send_buffer_size: self.builder.max_send_buffer_size,
13131361
reset_stream_duration: self.builder.reset_stream_duration,
13141362
reset_stream_max: self.builder.reset_stream_max,
1363+
remote_reset_stream_max: self.builder.pending_accept_reset_stream_max,
13151364
settings: self.builder.settings.clone(),
13161365
},
13171366
);

tests/h2-tests/tests/stream_states.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ async fn reset_streams_dont_grow_memory_continuously() {
200200
let (io, mut client) = mock::new();
201201

202202
const N: u32 = 50;
203+
const MAX: usize = 20;
203204

204205
let client = async move {
205206
let settings = client.assert_server_handshake().await;
@@ -212,14 +213,15 @@ async fn reset_streams_dont_grow_memory_continuously() {
212213
}
213214
tokio::time::timeout(
214215
std::time::Duration::from_secs(1),
215-
client.recv_frame(frames::go_away(41).calm()),
216+
client.recv_frame(frames::go_away((MAX * 2 + 1) as u32).calm()),
216217
)
217218
.await
218219
.expect("client goaway");
219220
};
220221

221222
let srv = async move {
222223
let mut srv = server::Builder::new()
224+
.max_pending_accept_reset_streams(MAX)
223225
.handshake::<_, Bytes>(io)
224226
.await
225227
.expect("handshake");

0 commit comments

Comments
 (0)