Skip to content

Commit 86bcd4a

Browse files
committed
feat(profiler): support cancellation tokens in exporter cxx bindings
1 parent 5027515 commit 86bcd4a

File tree

2 files changed

+150
-5
lines changed

2 files changed

+150
-5
lines changed

examples/cxx/profiling.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ int main() {
184184
// Export to Datadog
185185
std::cout << "\n=== Exporting to Datadog ===" << std::endl;
186186

187+
// Create a cancellation token for the export
188+
// In a real application, you could clone this and cancel from another thread
189+
// Example: auto token_clone = cancel_token->clone_token(); token_clone->cancel();
190+
auto cancel_token = new_cancellation_token();
191+
187192
try {
188193
// Example: Create an additional file to attach (e.g., application metadata)
189194
std::string app_metadata = R"({
@@ -217,7 +222,7 @@ int main() {
217222

218223
std::cout << "Exporting profile to Datadog with additional metadata..." << std::endl;
219224

220-
exporter->send_profile(
225+
exporter->send_profile_with_cancellation(
221226
*profile,
222227
// Files to compress and attach
223228
{AttachmentFile{
@@ -234,7 +239,8 @@ int main() {
234239
// Internal metadata (JSON string)
235240
R"({"profiler_version": "1.0", "custom_field": "demo"})",
236241
// System info (JSON string)
237-
R"({"os": "macos", "arch": "arm64", "cores": 8})"
242+
R"({"os": "macos", "arch": "arm64", "cores": 8})",
243+
*cancel_token
238244
);
239245
std::cout << "✅ Profile exported successfully!" << std::endl;
240246
} else {
@@ -256,7 +262,7 @@ int main() {
256262

257263
std::cout << "Exporting profile to Datadog with additional metadata..." << std::endl;
258264

259-
exporter->send_profile(
265+
exporter->send_profile_with_cancellation(
260266
*profile,
261267
// Files to compress and attach
262268
{AttachmentFile{
@@ -273,7 +279,8 @@ int main() {
273279
// Internal metadata (JSON string)
274280
R"({"profiler_version": "1.0", "custom_field": "demo"})",
275281
// System info (JSON string)
276-
R"({"os": "macos", "arch": "arm64", "cores": 8})"
282+
R"({"os": "macos", "arch": "arm64", "cores": 8})",
283+
*cancel_token
277284
);
278285
std::cout << "✅ Profile exported successfully!" << std::endl;
279286
}

libdd-profiling/src/cxx.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)