@@ -18,7 +18,8 @@ use lol_html::{element, errors::RewritingError};
1818use std:: sync:: Arc ;
1919use tokio:: { io:: AsyncRead , task:: JoinHandle } ;
2020use tokio_util:: io:: ReaderStream ;
21- use tracing:: error;
21+ use tracing:: { Span , error, instrument} ;
22+ use tracing_futures:: Instrument as _;
2223
2324const CHANNEL_SIZE : usize = 64 ;
2425
@@ -36,6 +37,7 @@ pub(crate) enum RustdocRewritingError {
3637/// render the `rustdoc/` templates with the `html`.
3738/// The output is an HTML page which has not yet been UTF-8 validated.
3839/// In practice, the output should always be valid UTF-8.
40+ #[ instrument( skip_all, fields( memory_limit = max_allowed_memory_usage) ) ]
3941pub ( crate ) fn rewrite_rustdoc_html_stream < R > (
4042 template_data : Arc < TemplateData > ,
4143 mut reader : R ,
@@ -46,123 +48,132 @@ pub(crate) fn rewrite_rustdoc_html_stream<R>(
4648where
4749 R : AsyncRead + Unpin + Send + ' static ,
4850{
51+ let stream_span = Span :: current ( ) ;
52+
4953 stream ! ( {
5054 let ( input_sender, mut input_receiver) =
5155 tokio:: sync:: mpsc:: channel:: <Option <Bytes >>( CHANNEL_SIZE ) ;
5256 let ( result_sender, mut result_receiver) =
5357 tokio:: sync:: mpsc:: channel:: <Bytes >( CHANNEL_SIZE ) ;
5458
55- let join_handle: JoinHandle <anyhow:: Result <_>> = tokio:: spawn( async move {
56- // we're using the rendering threadpool to limit CPU usage on the server, and to
57- // offload potentially CPU intensive stuff from the tokio runtime.
58- // Also this lets us limit the threadpool size and through that the CPU usage.
59- template_data
60- . render_in_threadpool( move || {
61- use lol_html:: html_content:: { ContentType , Element } ;
62- use lol_html:: { HtmlRewriter , MemorySettings , Settings } ;
59+ let producer_span = tracing:: info_span!( "producer_task" ) ;
6360
64- let head_html = Head :: new( & data) . render( ) . unwrap( ) ;
65- let vendored_html = Vendored . render( ) . unwrap( ) ;
66- let body_html = Body . render( ) . unwrap( ) ;
67- let topbar_html = data. render( ) . unwrap( ) ;
61+ let join_handle: JoinHandle <anyhow:: Result <_>> = tokio:: spawn(
62+ async move {
63+ // we're using the rendering threadpool to limit CPU usage on the server, and to
64+ // offload potentially CPU intensive stuff from the tokio runtime.
65+ // Also this lets us limit the threadpool size and through that the CPU usage.
66+ let render_span = tracing:: info_span!( "render_task" ) ;
67+ template_data
68+ . render_in_threadpool( move || {
69+ use lol_html:: html_content:: { ContentType , Element } ;
70+ use lol_html:: { HtmlRewriter , MemorySettings , Settings } ;
6871
69- // Before: <body> ... rustdoc content ... </body>
70- // After:
71- // ```html
72- // <div id="rustdoc_body_wrapper" class="{{ rustdoc_body_class }}" tabindex="-1">
73- // ... rustdoc content ...
74- // </div>
75- // ```
76- let body_handler = |rustdoc_body_class: & mut Element | {
77- // Add the `rustdoc` classes to the html body
78- let mut tmp;
79- let klass = if let Some ( classes) = rustdoc_body_class. get_attribute( "class" )
80- {
81- tmp = classes;
82- tmp. push_str( " container-rustdoc" ) ;
83- & tmp
84- } else {
85- "container-rustdoc"
86- } ;
87- rustdoc_body_class. set_attribute( "class" , klass) ?;
88- rustdoc_body_class. set_attribute( "id" , "rustdoc_body_wrapper" ) ?;
89- rustdoc_body_class. set_attribute( "tabindex" , "-1" ) ?;
90- // Change the `body` to a `div`
91- rustdoc_body_class. set_tag_name( "div" ) ?;
92- // Prepend the askama content
93- rustdoc_body_class. prepend( & body_html, ContentType :: Html ) ;
94- // Wrap the transformed body and topbar into a <body> element
95- rustdoc_body_class
96- . before( r#"<body class="rustdoc-page">"# , ContentType :: Html ) ;
97- // Insert the topbar outside of the rustdoc div
98- rustdoc_body_class. before( & topbar_html, ContentType :: Html ) ;
99- // Finalize body with </body>
100- rustdoc_body_class. after( "</body>" , ContentType :: Html ) ;
72+ let head_html = Head :: new( & data) . render( ) . unwrap( ) ;
73+ let vendored_html = Vendored . render( ) . unwrap( ) ;
74+ let body_html = Body . render( ) . unwrap( ) ;
75+ let topbar_html = data. render( ) . unwrap( ) ;
10176
102- Ok ( ( ) )
103- } ;
77+ // Before: <body> ... rustdoc content ... </body>
78+ // After:
79+ // ```html
80+ // <div id="rustdoc_body_wrapper" class="{{ rustdoc_body_class }}" tabindex="-1">
81+ // ... rustdoc content ...
82+ // </div>
83+ // ```
84+ let body_handler = |rustdoc_body_class: & mut Element | {
85+ // Add the `rustdoc` classes to the html body
86+ let mut tmp;
87+ let klass =
88+ if let Some ( classes) = rustdoc_body_class. get_attribute( "class" ) {
89+ tmp = classes;
90+ tmp. push_str( " container-rustdoc" ) ;
91+ & tmp
92+ } else {
93+ "container-rustdoc"
94+ } ;
95+ rustdoc_body_class. set_attribute( "class" , klass) ?;
96+ rustdoc_body_class. set_attribute( "id" , "rustdoc_body_wrapper" ) ?;
97+ rustdoc_body_class. set_attribute( "tabindex" , "-1" ) ?;
98+ // Change the `body` to a `div`
99+ rustdoc_body_class. set_tag_name( "div" ) ?;
100+ // Prepend the askama content
101+ rustdoc_body_class. prepend( & body_html, ContentType :: Html ) ;
102+ // Wrap the transformed body and topbar into a <body> element
103+ rustdoc_body_class
104+ . before( r#"<body class="rustdoc-page">"# , ContentType :: Html ) ;
105+ // Insert the topbar outside of the rustdoc div
106+ rustdoc_body_class. before( & topbar_html, ContentType :: Html ) ;
107+ // Finalize body with </body>
108+ rustdoc_body_class. after( "</body>" , ContentType :: Html ) ;
104109
105- let settings = Settings {
106- element_content_handlers: vec![
107- // Append `style.css` stylesheet after all head elements.
108- element!( "head" , |head: & mut Element | {
109- head. append( & head_html, ContentType :: Html ) ;
110- Ok ( ( ) )
111- } ) ,
112- element!( "body" , body_handler) ,
113- // Append `vendored.css` before `rustdoc.css`, so that the duplicate copy of
114- // `normalize.css` will be overridden by the later version.
115- //
116- // Later rustdoc has `#mainThemeStyle` that could be used, but pre-2018 docs
117- // don't have this:
118- //
119- // https://github.com/rust-lang/rust/commit/003b2bc1c65251ec2fc80b78ed91c43fb35402ec
120- //
121- // Pre-2018 rustdoc also didn't have the resource suffix, but docs.rs was using a fork
122- // that had implemented it already then, so we can assume the css files are
123- // `<some path>/rustdoc-<some suffix>.css` and use the `-` to distinguish from the
124- // `rustdoc.static` path.
125- element!(
126- "link[rel='stylesheet'][href*='rustdoc-']" ,
127- move |rustdoc_css: & mut Element | {
128- rustdoc_css. before( & vendored_html, ContentType :: Html ) ;
110+ Ok ( ( ) )
111+ } ;
112+
113+ let settings = Settings {
114+ element_content_handlers: vec![
115+ // Append `style.css` stylesheet after all head elements.
116+ element!( "head" , |head: & mut Element | {
117+ head. append( & head_html, ContentType :: Html ) ;
129118 Ok ( ( ) )
130- }
131- ) ,
132- ] ,
133- memory_settings: MemorySettings {
134- max_allowed_memory_usage,
135- ..MemorySettings :: default ( )
136- } ,
137- ..Settings :: default ( )
138- } ;
119+ } ) ,
120+ element!( "body" , body_handler) ,
121+ // Append `vendored.css` before `rustdoc.css`, so that the duplicate copy of
122+ // `normalize.css` will be overridden by the later version.
123+ //
124+ // Later rustdoc has `#mainThemeStyle` that could be used, but pre-2018 docs
125+ // don't have this:
126+ //
127+ // https://github.com/rust-lang/rust/commit/003b2bc1c65251ec2fc80b78ed91c43fb35402ec
128+ //
129+ // Pre-2018 rustdoc also didn't have the resource suffix, but docs.rs was using a fork
130+ // that had implemented it already then, so we can assume the css files are
131+ // `<some path>/rustdoc-<some suffix>.css` and use the `-` to distinguish from the
132+ // `rustdoc.static` path.
133+ element!(
134+ "link[rel='stylesheet'][href*='rustdoc-']" ,
135+ move |rustdoc_css: & mut Element | {
136+ rustdoc_css. before( & vendored_html, ContentType :: Html ) ;
137+ Ok ( ( ) )
138+ }
139+ ) ,
140+ ] ,
141+ memory_settings: MemorySettings {
142+ max_allowed_memory_usage,
143+ ..MemorySettings :: default ( )
144+ } ,
145+ ..Settings :: default ( )
146+ } ;
139147
140- let mut rewriter = HtmlRewriter :: new( settings, move |chunk: & [ u8 ] | {
141- // send the result back to the main rewriter when its coming in.
142- // this can fail only when the receiver is dropped, in which case
143- // we exit this thread anyways.
144- let _ = result_sender. blocking_send( Bytes :: copy_from_slice( chunk) ) ;
145- } ) ;
146- while let Some ( chunk) = input_receiver
147- . blocking_recv( )
148- . ok_or_else( || anyhow!( "couldn't receive from input_receiver" ) ) ?
149- {
150- // receive data from the input receiver.
151- // `input_receiver` is a non-async one.
152- // Since we're in a normal background thread, we can use the blocking `.recv`
153- // here.
154- // We will get `None` when the reader is done reading,
155- // so that's our signal to exit this loop and call `rewriter.end()` below.
156- rewriter. write( & chunk) ?;
157- }
158- // finalize everything. Will trigger the output sink (and through that,
159- // sending data to the `result_sender`).
160- rewriter. end( ) ?;
161- Ok ( ( ) )
162- } )
163- . await ?;
164- Ok ( ( ) )
165- } ) ;
148+ let mut rewriter = HtmlRewriter :: new( settings, move |chunk: & [ u8 ] | {
149+ // send the result back to the main rewriter when its coming in.
150+ // this can fail only when the receiver is dropped, in which case
151+ // we exit this thread anyways.
152+ let _ = result_sender. blocking_send( Bytes :: copy_from_slice( chunk) ) ;
153+ } ) ;
154+ while let Some ( chunk) = input_receiver
155+ . blocking_recv( )
156+ . ok_or_else( || anyhow!( "couldn't receive from input_receiver" ) ) ?
157+ {
158+ // receive data from the input receiver.
159+ // `input_receiver` is a non-async one.
160+ // Since we're in a normal background thread, we can use the blocking `.recv`
161+ // here.
162+ // We will get `None` when the reader is done reading,
163+ // so that's our signal to exit this loop and call `rewriter.end()` below.
164+ rewriter. write( & chunk) ?;
165+ }
166+ // finalize everything. Will trigger the output sink (and through that,
167+ // sending data to the `result_sender`).
168+ rewriter. end( ) ?;
169+ Ok ( ( ) )
170+ } )
171+ . instrument( render_span)
172+ . await ?;
173+ Ok ( ( ) )
174+ }
175+ . instrument( producer_span) ,
176+ ) ;
166177
167178 let mut reader_stream = ReaderStream :: new( & mut reader) ;
168179 while let Some ( chunk) = reader_stream. next( ) . await {
@@ -221,6 +232,7 @@ where
221232 }
222233 } ) ?;
223234 } )
235+ . instrument ( stream_span)
224236}
225237
226238#[ cfg( test) ]
0 commit comments