Skip to content

Commit 83bd15d

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

File tree

2 files changed

+148
-5
lines changed

2 files changed

+148
-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: 137 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,35 @@ 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+
fn send_profile_with_cancellation(
192+
self: &ProfileExporter,
193+
profile: &mut Profile,
194+
files_to_compress: Vec<AttachmentFile>,
195+
additional_tags: Vec<Tag>,
196+
process_tags: &str,
197+
internal_metadata: &str,
198+
info: &str,
199+
cancel: &CancellationToken,
200+
) -> Result<()>;
165201
}
166202
}
167203

@@ -245,6 +281,50 @@ impl<'a> TryFrom<&ffi::Tag<'a>> for exporter::Tag {
245281
}
246282
}
247283

284+
// ============================================================================
285+
// CancellationToken - Wrapper around tokio_util::sync::CancellationToken
286+
// ============================================================================
287+
288+
pub struct CancellationToken {
289+
inner: tokio_util::sync::CancellationToken,
290+
}
291+
292+
/// Creates a new cancellation token.
293+
pub fn new_cancellation_token() -> Box<CancellationToken> {
294+
Box::new(CancellationToken {
295+
inner: tokio_util::sync::CancellationToken::new(),
296+
})
297+
}
298+
299+
impl CancellationToken {
300+
/// Clones the cancellation token.
301+
///
302+
/// A cloned token is connected to the original token - either can be used
303+
/// to cancel or check cancellation status. The useful part is that they have
304+
/// independent lifetimes and can be dropped separately.
305+
///
306+
/// This is useful for multi-threaded scenarios where one thread performs the
307+
/// send operation while another thread can cancel it.
308+
pub fn clone_token(&self) -> Box<CancellationToken> {
309+
Box::new(CancellationToken {
310+
inner: self.inner.clone(),
311+
})
312+
}
313+
314+
/// Cancels the token.
315+
///
316+
/// Note that cancellation is a terminal state; calling cancel multiple times
317+
/// has no additional effect.
318+
pub fn cancel(&self) {
319+
self.inner.cancel();
320+
}
321+
322+
/// Returns true if the token has been cancelled.
323+
pub fn is_cancelled(&self) -> bool {
324+
self.inner.is_cancelled()
325+
}
326+
}
327+
248328
// ============================================================================
249329
// Profile - Wrapper around internal::Profile
250330
// ============================================================================
@@ -455,6 +535,62 @@ impl ProfileExporter {
455535
process_tags: &str,
456536
internal_metadata: &str,
457537
info: &str,
538+
) -> anyhow::Result<()> {
539+
self.send_profile_impl(
540+
profile,
541+
files_to_compress,
542+
additional_tags,
543+
process_tags,
544+
internal_metadata,
545+
info,
546+
None,
547+
)
548+
}
549+
550+
/// Sends a profile to Datadog with cancellation support.
551+
///
552+
/// # Arguments
553+
/// * `profile` - Profile to send (will be reset after sending)
554+
/// * `files_to_compress` - Additional files to compress and attach
555+
/// * `additional_tags` - Per-profile tags (in addition to exporter-level tags)
556+
/// * `process_tags` - Process-level tags as comma-separated string. Empty string if not needed.
557+
/// * `internal_metadata` - Internal metadata as JSON string. Empty string if not needed.
558+
/// Example: `{"custom_field": "value", "version": "1.0"}`
559+
/// * `info` - System/environment info as JSON string. Empty string if not needed. Example:
560+
/// `{"os": "linux", "arch": "x86_64", "kernel": "5.15.0"}`
561+
/// * `cancel` - Cancellation token to cancel the send operation
562+
pub fn send_profile_with_cancellation(
563+
&self,
564+
profile: &mut Profile,
565+
files_to_compress: Vec<ffi::AttachmentFile>,
566+
additional_tags: Vec<ffi::Tag>,
567+
process_tags: &str,
568+
internal_metadata: &str,
569+
info: &str,
570+
cancel: &CancellationToken,
571+
) -> anyhow::Result<()> {
572+
self.send_profile_impl(
573+
profile,
574+
files_to_compress,
575+
additional_tags,
576+
process_tags,
577+
internal_metadata,
578+
info,
579+
Some(&cancel.inner),
580+
)
581+
}
582+
583+
/// Internal implementation shared by send_profile and send_profile_with_cancellation
584+
#[allow(clippy::too_many_arguments)]
585+
fn send_profile_impl(
586+
&self,
587+
profile: &mut Profile,
588+
files_to_compress: Vec<ffi::AttachmentFile>,
589+
additional_tags: Vec<ffi::Tag>,
590+
process_tags: &str,
591+
internal_metadata: &str,
592+
info: &str,
593+
cancel: Option<&tokio_util::sync::CancellationToken>,
458594
) -> anyhow::Result<()> {
459595
// Reset the profile and get the old one to export
460596
let old_profile = profile.inner.reset_and_return_previous()?;
@@ -506,7 +642,7 @@ impl ProfileExporter {
506642
internal_metadata_json,
507643
info_json,
508644
)?;
509-
let response = self.inner.send(request, None)?;
645+
let response = self.inner.send(request, cancel)?;
510646

511647
// Check response status
512648
if !response.status().is_success() {

0 commit comments

Comments
 (0)