@@ -74,6 +74,13 @@ pub mod ffi {
7474 extern "Rust" {
7575 type Profile ;
7676 type ProfileExporter ;
77+ type CancellationToken ;
78+
79+ // CancellationToken factory and methods
80+ fn new_cancellation_token ( ) -> Box < CancellationToken > ;
81+ fn clone_token ( self : & CancellationToken ) -> Box < CancellationToken > ;
82+ fn cancel ( self : & CancellationToken ) ;
83+ fn is_cancelled ( self : & CancellationToken ) -> bool ;
7784
7885 // Static factory methods for Profile
7986 #[ Self = "Profile" ]
@@ -162,6 +169,36 @@ pub mod ffi {
162169 internal_metadata : & str ,
163170 info : & str ,
164171 ) -> Result < ( ) > ;
172+
173+ /// Sends a profile to Datadog with cancellation support.
174+ ///
175+ /// This is the same as `send_profile`, but allows cancelling the operation from another
176+ /// thread using a cancellation token.
177+ ///
178+ /// # Arguments
179+ /// * `profile` - Profile to send (will be reset after sending)
180+ /// * `files_to_compress` - Additional files to compress and attach (e.g., heap dumps)
181+ /// * `additional_tags` - Per-profile tags (in addition to exporter-level tags)
182+ /// * `process_tags` - Process-level tags as comma-separated string (e.g.,
183+ /// "runtime:native,profiler_version:1.0") Pass empty string "" if not needed
184+ /// * `internal_metadata` - Internal metadata as JSON string (e.g., `{"key": "value"}`) See
185+ /// Datadog-internal "RFC: Attaching internal metadata to pprof profiles" Pass empty
186+ /// string "" if not needed
187+ /// * `info` - System/environment info as JSON string (e.g., `{"os": "linux", "arch":
188+ /// "x86_64"}`) See Datadog-internal "RFC: Pprof System Info Support" Pass empty string ""
189+ /// if not needed
190+ /// * `cancel` - Cancellation token to cancel the send operation
191+ #[ allow( clippy:: too_many_arguments) ]
192+ fn send_profile_with_cancellation (
193+ self : & ProfileExporter ,
194+ profile : & mut Profile ,
195+ files_to_compress : Vec < AttachmentFile > ,
196+ additional_tags : Vec < Tag > ,
197+ process_tags : & str ,
198+ internal_metadata : & str ,
199+ info : & str ,
200+ cancel : & CancellationToken ,
201+ ) -> Result < ( ) > ;
165202 }
166203}
167204
@@ -245,6 +282,50 @@ impl<'a> TryFrom<&ffi::Tag<'a>> for exporter::Tag {
245282 }
246283}
247284
285+ // ============================================================================
286+ // CancellationToken - Wrapper around tokio_util::sync::CancellationToken
287+ // ============================================================================
288+
289+ pub struct CancellationToken {
290+ inner : tokio_util:: sync:: CancellationToken ,
291+ }
292+
293+ /// Creates a new cancellation token.
294+ pub fn new_cancellation_token ( ) -> Box < CancellationToken > {
295+ Box :: new ( CancellationToken {
296+ inner : tokio_util:: sync:: CancellationToken :: new ( ) ,
297+ } )
298+ }
299+
300+ impl CancellationToken {
301+ /// Clones the cancellation token.
302+ ///
303+ /// A cloned token is connected to the original token - either can be used
304+ /// to cancel or check cancellation status. The useful part is that they have
305+ /// independent lifetimes and can be dropped separately.
306+ ///
307+ /// This is useful for multi-threaded scenarios where one thread performs the
308+ /// send operation while another thread can cancel it.
309+ pub fn clone_token ( & self ) -> Box < CancellationToken > {
310+ Box :: new ( CancellationToken {
311+ inner : self . inner . clone ( ) ,
312+ } )
313+ }
314+
315+ /// Cancels the token.
316+ ///
317+ /// Note that cancellation is a terminal state; calling cancel multiple times
318+ /// has no additional effect.
319+ pub fn cancel ( & self ) {
320+ self . inner . cancel ( ) ;
321+ }
322+
323+ /// Returns true if the token has been cancelled.
324+ pub fn is_cancelled ( & self ) -> bool {
325+ self . inner . is_cancelled ( )
326+ }
327+ }
328+
248329// ============================================================================
249330// Profile - Wrapper around internal::Profile
250331// ============================================================================
@@ -455,6 +536,63 @@ impl ProfileExporter {
455536 process_tags : & str ,
456537 internal_metadata : & str ,
457538 info : & str ,
539+ ) -> anyhow:: Result < ( ) > {
540+ self . send_profile_impl (
541+ profile,
542+ files_to_compress,
543+ additional_tags,
544+ process_tags,
545+ internal_metadata,
546+ info,
547+ None ,
548+ )
549+ }
550+
551+ /// Sends a profile to Datadog with cancellation support.
552+ ///
553+ /// # Arguments
554+ /// * `profile` - Profile to send (will be reset after sending)
555+ /// * `files_to_compress` - Additional files to compress and attach
556+ /// * `additional_tags` - Per-profile tags (in addition to exporter-level tags)
557+ /// * `process_tags` - Process-level tags as comma-separated string. Empty string if not needed.
558+ /// * `internal_metadata` - Internal metadata as JSON string. Empty string if not needed.
559+ /// Example: `{"custom_field": "value", "version": "1.0"}`
560+ /// * `info` - System/environment info as JSON string. Empty string if not needed. Example:
561+ /// `{"os": "linux", "arch": "x86_64", "kernel": "5.15.0"}`
562+ /// * `cancel` - Cancellation token to cancel the send operation
563+ #[ allow( clippy:: too_many_arguments) ]
564+ pub fn send_profile_with_cancellation (
565+ & self ,
566+ profile : & mut Profile ,
567+ files_to_compress : Vec < ffi:: AttachmentFile > ,
568+ additional_tags : Vec < ffi:: Tag > ,
569+ process_tags : & str ,
570+ internal_metadata : & str ,
571+ info : & str ,
572+ cancel : & CancellationToken ,
573+ ) -> anyhow:: Result < ( ) > {
574+ self . send_profile_impl (
575+ profile,
576+ files_to_compress,
577+ additional_tags,
578+ process_tags,
579+ internal_metadata,
580+ info,
581+ Some ( & cancel. inner ) ,
582+ )
583+ }
584+
585+ /// Internal implementation shared by send_profile and send_profile_with_cancellation
586+ #[ allow( clippy:: too_many_arguments) ]
587+ fn send_profile_impl (
588+ & self ,
589+ profile : & mut Profile ,
590+ files_to_compress : Vec < ffi:: AttachmentFile > ,
591+ additional_tags : Vec < ffi:: Tag > ,
592+ process_tags : & str ,
593+ internal_metadata : & str ,
594+ info : & str ,
595+ cancel : Option < & tokio_util:: sync:: CancellationToken > ,
458596 ) -> anyhow:: Result < ( ) > {
459597 // Reset the profile and get the old one to export
460598 let old_profile = profile. inner . reset_and_return_previous ( ) ?;
@@ -506,7 +644,7 @@ impl ProfileExporter {
506644 internal_metadata_json,
507645 info_json,
508646 ) ?;
509- let response = self . inner . send ( request, None ) ?;
647+ let response = self . inner . send ( request, cancel ) ?;
510648
511649 // Check response status
512650 if !response. status ( ) . is_success ( ) {
0 commit comments