@@ -29,7 +29,7 @@ use anyhow::Context;
2929use libdd_common:: tag:: Tag ;
3030use libdd_common:: { azure_app_services, tag, Endpoint } ;
3131use serde_json:: json;
32- use std:: { future , io:: Write } ;
32+ use std:: io:: Write ;
3333use tokio:: runtime:: Runtime ;
3434use tokio_util:: sync:: CancellationToken ;
3535
@@ -57,6 +57,13 @@ impl ProfileExporter {
5757 /// The default configuration automatically retries safe errors and low-level protocol NACKs.
5858 /// For custom retry policies, users can configure the reqwest client before creating the
5959 /// exporter.
60+ ///
61+ /// # Thread Safety
62+ ///
63+ /// The exporter can be used from any thread, but if using `send_blocking()`, the exporter
64+ /// should remain on the same thread for all blocking calls. See [`send_blocking`] for details.
65+ ///
66+ /// [`send_blocking`]: ProfileExporter::send_blocking
6067 pub fn new (
6168 profiling_library_name : & str ,
6269 profiling_library_version : & str ,
@@ -176,6 +183,23 @@ impl ProfileExporter {
176183 } )
177184 }
178185
186+ /// Synchronously sends a profile to the configured endpoint.
187+ ///
188+ /// This is a blocking wrapper around the async [`send`] method. It lazily creates and caches
189+ /// a single-threaded tokio runtime on first use.
190+ ///
191+ /// # Thread Affinity
192+ ///
193+ /// **Important**: The cached runtime uses `new_current_thread()`, which has thread affinity.
194+ /// For best results, all calls to `send_blocking()` on the same exporter instance should be
195+ /// made from the same thread. Moving the exporter across threads between blocking calls may
196+ /// cause issues.
197+ ///
198+ /// If you need to use the exporter from multiple threads, consider either:
199+ /// - Creating a separate exporter instance per thread
200+ /// - Using the async [`send`] method directly from within a tokio runtime
201+ ///
202+ /// [`send`]: ProfileExporter::send
179203 #[ allow( clippy:: too_many_arguments) ]
180204 pub fn send_blocking (
181205 & mut self ,
@@ -241,17 +265,14 @@ impl ProfileExporter {
241265 . multipart ( form)
242266 . build ( ) ?;
243267
244- // Send request with cancellation support
245- tokio:: select! {
246- _ = async {
247- match cancel {
248- Some ( token) => token. cancelled( ) . await ,
249- None => future:: pending( ) . await ,
250- }
251- } => Err ( anyhow:: anyhow!( "Operation cancelled by user" ) ) ,
252- result = self . client. execute( request) => {
253- Ok ( result?. status( ) )
268+ // Send request with optional cancellation support
269+ if let Some ( token) = cancel {
270+ tokio:: select! {
271+ _ = token. cancelled( ) => anyhow:: bail!( "Operation cancelled by user" ) ,
272+ result = self . client. execute( request) => Ok ( result?. status( ) ) ,
254273 }
274+ } else {
275+ Ok ( self . client . execute ( request) . await ?. status ( ) )
255276 }
256277 }
257278
0 commit comments