diff --git a/libdd-profiling-ffi/src/exporter.rs b/libdd-profiling-ffi/src/exporter.rs index 234a6d55ce..51a984c782 100644 --- a/libdd-profiling-ffi/src/exporter.rs +++ b/libdd-profiling-ffi/src/exporter.rs @@ -222,8 +222,38 @@ unsafe fn parse_json( } } +/// Initializes the tokio runtime for the exporter. +/// +/// This function creates the tokio runtime used by `ddog_prof_Exporter_send_blocking`. +/// It can be called ahead of time to ensure the runtime is ready before sending. +/// +/// # Thread Affinity +/// +/// **Important**: The runtime has thread affinity. This function should be called from +/// the same thread that will later call `ddog_prof_Exporter_send_blocking`. +/// +/// # Arguments +/// * `exporter` - Borrows the exporter. +/// +/// # Safety +/// The `exporter` must point to a valid ProfileExporter that has not been dropped. +#[no_mangle] +#[must_use] +#[named] +pub unsafe extern "C" fn ddog_prof_Exporter_init_runtime( + mut exporter: *mut Handle, +) -> VoidResult { + wrap_with_void_ffi_result!({ + let exporter = exporter.to_inner_mut()?; + exporter.init_runtime()? + }) +} + /// Builds a request and sends it, returning the HttpStatus. /// +/// Note: If the runtime has not been initialized via `ddog_prof_Exporter_init_runtime`, +/// it will be lazily initialized on first call. +/// /// # Arguments /// * `exporter` - Borrows the exporter. /// * `profile` - Takes ownership of the profile. diff --git a/libdd-profiling/src/exporter/profile_exporter.rs b/libdd-profiling/src/exporter/profile_exporter.rs index 1cc812839b..6cd526bbc1 100644 --- a/libdd-profiling/src/exporter/profile_exporter.rs +++ b/libdd-profiling/src/exporter/profile_exporter.rs @@ -152,6 +152,26 @@ impl ProfileExporter { /// - Using the async [`send`] method directly from within a tokio runtime /// /// [`send`]: ProfileExporter::send + /// Initializes the tokio runtime for blocking operations. + /// + /// This method lazily creates a single-threaded tokio runtime. It can be called + /// before `send_blocking` to ensure the runtime is ready ahead of time. + /// + /// # Thread Affinity + /// + /// **Important**: The runtime uses `new_current_thread()`, which has thread affinity. + /// This method should be called from the same thread that will later call `send_blocking`. + pub fn init_runtime(&mut self) -> anyhow::Result<()> { + if self.runtime.is_none() { + self.runtime = Some( + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?, + ); + } + Ok(()) + } + #[allow(clippy::too_many_arguments)] pub fn send_blocking( &mut self, @@ -163,13 +183,7 @@ impl ProfileExporter { process_tags: Option<&str>, cancel: Option<&CancellationToken>, ) -> anyhow::Result { - if self.runtime.is_none() { - self.runtime = Some( - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build()?, - ); - } + self.init_runtime()?; Ok(self .runtime