@@ -8,12 +8,63 @@ use http::Request;
88
99use std:: {
1010 error:: Error ,
11+ future:: Future ,
12+ pin:: Pin ,
13+ task:: { Context , Poll } ,
1114 time:: { Duration , Instant } ,
1215} ;
1316
1417use tokio:: net:: { TcpListener , TcpStream } ;
1518
1619const NUM_REQUESTS_TO_SEND : usize = 100_000 ;
20+ const WRITE_CONTENTION_STREAMS : usize = 512 ;
21+ const WRITE_CONTENTION_CHUNKS_PER_STREAM : usize = 128 ;
22+ const WRITE_CONTENTION_CHUNK_SIZE : usize = 16 * 1024 ;
23+ const WRITE_CONTENTION_WINDOW : u32 = 16 * 1024 * 1024 ;
24+ const WRITE_CONTENTION_MAX_FRAME_SIZE : u32 = 64 * 1024 ;
25+ const WRITE_CONTENTION_MAX_SEND_BUFFER : usize = 8 * 1024 * 1024 ;
26+ const WRITE_CONTENTION_WORKER_THREADS : usize = 4 ;
27+
28+ #[ derive( Clone , Copy ) ]
29+ struct WriteContentionConfig {
30+ streams : usize ,
31+ chunks_per_stream : usize ,
32+ chunk_size : usize ,
33+ }
34+
35+ impl WriteContentionConfig {
36+ fn from_env ( ) -> Self {
37+ Self {
38+ streams : env_usize ( "H2_WRITE_CONTENTION_STREAMS" , WRITE_CONTENTION_STREAMS ) ,
39+ chunks_per_stream : env_usize (
40+ "H2_WRITE_CONTENTION_CHUNKS_PER_STREAM" ,
41+ WRITE_CONTENTION_CHUNKS_PER_STREAM ,
42+ ) ,
43+ chunk_size : env_usize (
44+ "H2_WRITE_CONTENTION_CHUNK_SIZE" ,
45+ WRITE_CONTENTION_CHUNK_SIZE ,
46+ ) ,
47+ }
48+ }
49+
50+ fn bytes ( & self ) -> usize {
51+ self . streams * self . chunks_per_stream * self . chunk_size
52+ }
53+ }
54+
55+ fn write_contention_worker_threads ( ) -> usize {
56+ env_usize (
57+ "H2_WRITE_CONTENTION_WORKER_THREADS" ,
58+ WRITE_CONTENTION_WORKER_THREADS ,
59+ )
60+ }
61+
62+ fn env_usize ( name : & str , default : usize ) -> usize {
63+ std:: env:: var ( name)
64+ . ok ( )
65+ . and_then ( |value| value. parse ( ) . ok ( ) )
66+ . unwrap_or ( default)
67+ }
1768
1869// The actual server.
1970async fn server ( addr : & str ) -> Result < ( ) , Box < dyn Error + Send + Sync > > {
@@ -111,8 +162,174 @@ async fn send_requests(addr: &str) -> Result<(), Box<dyn Error>> {
111162 Ok ( ( ) )
112163}
113164
165+ async fn write_contention_benchmark ( ) -> Result < ( ) , Box < dyn Error > > {
166+ let config = WriteContentionConfig :: from_env ( ) ;
167+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . await ?;
168+ let addr = listener. local_addr ( ) ?;
169+
170+ println ! (
171+ "H2 write contention: {} streams x {} chunks x {}B = {:.1} MiB" ,
172+ config. streams,
173+ config. chunks_per_stream,
174+ config. chunk_size,
175+ config. bytes( ) as f64 / ( 1024.0 * 1024.0 ) ,
176+ ) ;
177+
178+ tokio:: spawn ( async move {
179+ let ( socket, _peer_addr) = listener. accept ( ) . await . unwrap ( ) ;
180+ if let Err ( e) = serve_write_contention ( socket, config) . await {
181+ println ! ( "write contention server error: {e:?}" ) ;
182+ }
183+ } ) ;
184+
185+ let tcp = TcpStream :: connect ( addr) . await ?;
186+ let mut builder = client:: Builder :: new ( ) ;
187+ builder
188+ . initial_window_size ( WRITE_CONTENTION_WINDOW )
189+ . initial_connection_window_size ( WRITE_CONTENTION_WINDOW )
190+ . max_frame_size ( WRITE_CONTENTION_MAX_FRAME_SIZE )
191+ . max_concurrent_streams ( config. streams as u32 )
192+ . max_send_buffer_size ( WRITE_CONTENTION_MAX_SEND_BUFFER ) ;
193+
194+ let ( client, h2) = builder. handshake :: < _ , Bytes > ( tcp) . await ?;
195+ tokio:: spawn ( async move {
196+ if let Err ( e) = h2. await {
197+ println ! ( "write contention client connection error: {e:?}" ) ;
198+ }
199+ } ) ;
200+
201+ let mut handles = Vec :: with_capacity ( config. streams ) ;
202+ let started = Instant :: now ( ) ;
203+ for _ in 0 ..config. streams {
204+ let client = client. clone ( ) ;
205+ let expected = config. chunks_per_stream * config. chunk_size ;
206+ handles. push ( tokio:: spawn ( async move {
207+ let request = Request :: builder ( ) . body ( ( ) ) . unwrap ( ) ;
208+ let mut client = client. ready ( ) . await . unwrap ( ) ;
209+ let ( response, _) = client. send_request ( request, true ) . unwrap ( ) ;
210+ let response = response. await . unwrap ( ) ;
211+ let mut body = response. into_body ( ) ;
212+ let mut received = 0 ;
213+
214+ while let Some ( chunk) = body. data ( ) . await {
215+ let chunk = chunk. unwrap ( ) ;
216+ received += chunk. len ( ) ;
217+ let _ = body. flow_control ( ) . release_capacity ( chunk. len ( ) ) ;
218+ }
219+
220+ assert_eq ! ( received, expected) ;
221+ } ) ) ;
222+ }
223+
224+ for handle in handles {
225+ handle. await . unwrap ( ) ;
226+ }
227+
228+ let elapsed = started. elapsed ( ) ;
229+ let mib = config. bytes ( ) as f64 / ( 1024.0 * 1024.0 ) ;
230+ println ! ( "Overall: {}ms." , elapsed. as_millis( ) ) ;
231+ println ! ( "Throughput: {:.1} MiB/s" , mib / elapsed. as_secs_f64( ) ) ;
232+
233+ Ok ( ( ) )
234+ }
235+
236+ async fn serve_write_contention (
237+ socket : TcpStream ,
238+ config : WriteContentionConfig ,
239+ ) -> Result < ( ) , Box < dyn Error + Send + Sync > > {
240+ let mut builder = server:: Builder :: new ( ) ;
241+ builder
242+ . initial_window_size ( WRITE_CONTENTION_WINDOW )
243+ . initial_connection_window_size ( WRITE_CONTENTION_WINDOW )
244+ . max_frame_size ( WRITE_CONTENTION_MAX_FRAME_SIZE )
245+ . max_concurrent_streams ( config. streams as u32 )
246+ . max_send_buffer_size ( WRITE_CONTENTION_MAX_SEND_BUFFER ) ;
247+
248+ let mut connection = builder. handshake ( socket) . await ?;
249+ while let Some ( result) = connection. accept ( ) . await {
250+ let ( request, respond) = result?;
251+ tokio:: spawn ( async move {
252+ if let Err ( e) = handle_write_contention_request ( request, respond, config) . await {
253+ println ! ( "write contention request error: {e}" ) ;
254+ }
255+ } ) ;
256+ }
257+
258+ Ok ( ( ) )
259+ }
260+
261+ async fn handle_write_contention_request (
262+ mut request : Request < RecvStream > ,
263+ mut respond : SendResponse < Bytes > ,
264+ config : WriteContentionConfig ,
265+ ) -> Result < ( ) , Box < dyn Error + Send + Sync > > {
266+ let body = request. body_mut ( ) ;
267+ while let Some ( data) = body. data ( ) . await {
268+ let data = data?;
269+ let _ = body. flow_control ( ) . release_capacity ( data. len ( ) ) ;
270+ }
271+
272+ let response = http:: Response :: new ( ( ) ) ;
273+ let mut send = respond. send_response ( response, false ) ?;
274+ let chunk = Bytes :: from ( vec ! [ b'x' ; config. chunk_size] ) ;
275+
276+ for idx in 0 ..config. chunks_per_stream {
277+ let end_of_stream = idx + 1 == config. chunks_per_stream ;
278+ send_chunk ( & mut send, chunk. clone ( ) , end_of_stream) . await ?;
279+ }
280+
281+ Ok ( ( ) )
282+ }
283+
284+ async fn send_chunk (
285+ send : & mut h2:: SendStream < Bytes > ,
286+ chunk : Bytes ,
287+ end_of_stream : bool ,
288+ ) -> Result < ( ) , h2:: Error > {
289+ let len = chunk. len ( ) ;
290+ send. reserve_capacity ( len) ;
291+ loop {
292+ if send. capacity ( ) >= len {
293+ send. send_data ( chunk, end_of_stream) ?;
294+ return Ok ( ( ) ) ;
295+ }
296+
297+ match ( Capacity { send } ) . await {
298+ Some ( Ok ( _) ) => { }
299+ Some ( Err ( err) ) => return Err ( err) ,
300+ None => return Err ( h2:: Reason :: INTERNAL_ERROR . into ( ) ) ,
301+ }
302+ }
303+ }
304+
305+ struct Capacity < ' a > {
306+ send : & ' a mut h2:: SendStream < Bytes > ,
307+ }
308+
309+ impl Future for Capacity < ' _ > {
310+ type Output = Option < Result < usize , h2:: Error > > ;
311+
312+ fn poll ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
313+ self . send . poll_capacity ( cx)
314+ }
315+ }
316+
114317fn main ( ) {
115318 let _ = env_logger:: try_init ( ) ;
319+ let bench = std:: env:: var ( "H2_BENCH" ) . unwrap_or_else ( |_| "all" . to_string ( ) ) ;
320+
321+ if bench == "write-contention" {
322+ let worker_threads = write_contention_worker_threads ( ) ;
323+ println ! ( "H2 write contention worker threads: {worker_threads}" ) ;
324+ let rt = tokio:: runtime:: Builder :: new_multi_thread ( )
325+ . worker_threads ( worker_threads)
326+ . enable_all ( )
327+ . build ( )
328+ . unwrap ( ) ;
329+ rt. block_on ( write_contention_benchmark ( ) ) . unwrap ( ) ;
330+ return ;
331+ }
332+
116333 let addr = "127.0.0.1:5928" ;
117334 println ! ( "H2 running in current-thread runtime at {addr}:" ) ;
118335 std:: thread:: spawn ( || {
@@ -145,4 +362,15 @@ fn main() {
145362 . build ( )
146363 . unwrap ( ) ;
147364 rt. block_on ( send_requests ( addr) ) . unwrap ( ) ;
365+
366+ if bench == "all" {
367+ let worker_threads = write_contention_worker_threads ( ) ;
368+ println ! ( "H2 write contention worker threads: {worker_threads}" ) ;
369+ let rt = tokio:: runtime:: Builder :: new_multi_thread ( )
370+ . worker_threads ( worker_threads)
371+ . enable_all ( )
372+ . build ( )
373+ . unwrap ( ) ;
374+ rt. block_on ( write_contention_benchmark ( ) ) . unwrap ( ) ;
375+ }
148376}
0 commit comments