@@ -65,8 +65,6 @@ pub(crate) const OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT: usize = 512;
6565pub ( crate ) const OTEL_BSP_EXPORT_TIMEOUT : & str = "OTEL_BSP_EXPORT_TIMEOUT" ;
6666/// Default maximum allowed time to export data.
6767pub ( crate ) const OTEL_BSP_EXPORT_TIMEOUT_DEFAULT : Duration = Duration :: from_millis ( 30_000 ) ;
68- /// Environment variable to configure max concurrent exports for batch span
69- /// processor.
7068pub ( crate ) const OTEL_BSP_MAX_CONCURRENT_EXPORTS : & str = "OTEL_BSP_MAX_CONCURRENT_EXPORTS" ;
7169/// Default max concurrent exports for BSP
7270pub ( crate ) const OTEL_BSP_MAX_CONCURRENT_EXPORTS_DEFAULT : usize = 1 ;
@@ -786,11 +784,6 @@ pub struct BatchConfig {
786784 pub ( crate ) max_export_timeout : Duration ,
787785
788786 #[ allow( dead_code) ]
789- /// Maximum number of concurrent exports
790- ///
791- /// Limits the number of spawned tasks for exports and thus memory consumed
792- /// by an exporter. A value of 1 will cause exports to be performed
793- /// synchronously on the BatchSpanProcessor task.
794787 pub ( crate ) max_concurrent_exports : usize ,
795788}
796789
@@ -863,15 +856,20 @@ impl BatchConfigBuilder {
863856
864857 #[ cfg( feature = "experimental_trace_batch_span_processor_with_async_runtime" ) ]
865858 /// Set max_concurrent_exports for [`BatchConfigBuilder`].
866- /// It's the maximum number of concurrent exports.
867- /// Limits the number of spawned tasks for exports and thus memory consumed by an exporter.
868- /// The default value is 1.
869- /// If the max_concurrent_exports value is default value, it will cause exports to be performed
870- /// synchronously on the BatchSpanProcessor task.
871- /// The default value is 1.
859+ ///
860+ /// This value is honored by
861+ /// `span_processor_with_async_runtime::BatchSpanProcessor`, where it limits
862+ /// the number of concurrent export tasks.
863+ ///
864+ /// The thread-based `BatchSpanProcessor` exports serially and ignores this
865+ /// setting.
872866 ///
873867 /// Corresponding environment variable: `OTEL_BSP_MAX_CONCURRENT_EXPORTS`.
874868 ///
869+ /// For concurrent exports, enable
870+ /// `experimental_trace_batch_span_processor_with_async_runtime` and use the
871+ /// async-runtime processor.
872+ ///
875873 /// Note: Programmatically setting this will override any value set via the environment variable.
876874 pub fn with_max_concurrent_exports ( mut self , max_concurrent_exports : usize ) -> Self {
877875 self . max_concurrent_exports = max_concurrent_exports;
@@ -1181,7 +1179,10 @@ mod tests {
11811179
11821180 use crate :: Resource ;
11831181 use opentelemetry:: { Key , KeyValue , Value } ;
1184- use std:: sync:: { atomic:: Ordering , Arc , Mutex } ;
1182+ use std:: sync:: {
1183+ atomic:: { AtomicUsize , Ordering } ,
1184+ Arc , Mutex ,
1185+ } ;
11851186
11861187 // Mock exporter to test functionality
11871188 #[ derive( Debug ) ]
@@ -1356,6 +1357,67 @@ mod tests {
13561357 ) ;
13571358 }
13581359
1360+ #[ test]
1361+ fn batchspanprocessor_sync_ignores_max_concurrent_exports ( ) {
1362+ #[ derive( Debug ) ]
1363+ struct TrackingExporter {
1364+ active : Arc < AtomicUsize > ,
1365+ max_inflight : Arc < AtomicUsize > ,
1366+ export_calls : Arc < AtomicUsize > ,
1367+ delay : Duration ,
1368+ }
1369+
1370+ impl SpanExporter for TrackingExporter {
1371+ async fn export ( & self , _batch : Vec < SpanData > ) -> OTelSdkResult {
1372+ self . export_calls . fetch_add ( 1 , Ordering :: SeqCst ) ;
1373+ let inflight = self . active . fetch_add ( 1 , Ordering :: SeqCst ) + 1 ;
1374+ self . max_inflight . fetch_max ( inflight, Ordering :: SeqCst ) ;
1375+
1376+ std:: thread:: sleep ( self . delay ) ;
1377+ self . active . fetch_sub ( 1 , Ordering :: SeqCst ) ;
1378+ Ok ( ( ) )
1379+ }
1380+ }
1381+
1382+ let active = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
1383+ let max_inflight = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
1384+ let export_calls = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
1385+ let exporter = TrackingExporter {
1386+ active : active. clone ( ) ,
1387+ max_inflight : max_inflight. clone ( ) ,
1388+ export_calls : export_calls. clone ( ) ,
1389+ delay : Duration :: from_millis ( 50 ) ,
1390+ } ;
1391+
1392+ let config = BatchConfig {
1393+ max_export_batch_size : 1 ,
1394+ max_queue_size : 16 ,
1395+ scheduled_delay : Duration :: from_secs ( 3600 ) ,
1396+ max_export_timeout : Duration :: from_secs ( 5 ) ,
1397+ max_concurrent_exports : 4 ,
1398+ } ;
1399+
1400+ let processor = BatchSpanProcessor :: new ( exporter, config) ;
1401+
1402+ processor. on_end ( new_test_export_span_data ( ) ) ;
1403+ processor. on_end ( new_test_export_span_data ( ) ) ;
1404+ processor. on_end ( new_test_export_span_data ( ) ) ;
1405+
1406+ processor. force_flush ( ) . expect ( "force flush failed" ) ;
1407+ processor. shutdown ( ) . expect ( "shutdown failed" ) ;
1408+
1409+ assert_eq ! (
1410+ export_calls. load( Ordering :: SeqCst ) ,
1411+ 3 ,
1412+ "expected three exports for three spans with max_export_batch_size=1"
1413+ ) ;
1414+ assert_eq ! (
1415+ max_inflight. load( Ordering :: SeqCst ) ,
1416+ 1 ,
1417+ "sync BatchSpanProcessor should export serially regardless of max_concurrent_exports"
1418+ ) ;
1419+ }
1420+
13591421 #[ test]
13601422 fn validate_span_attributes_exported_correctly ( ) {
13611423 let exporter = MockSpanExporter :: new ( ) ;
0 commit comments