Skip to content

Commit 3757554

Browse files
authored
feat(profiling-ffi): ProfilesDictionary (#1405)
feat(profiling-ffi): ProfilesDictionary chore: add cbindgen renames for ProfilesDictionary FFI types - ArcHandle<ProfilesDictionary> -> ddog_prof_ProfilesDictionaryHandle - ProfileStatus -> ddog_prof_Status refactor: avoid unnecessary unsafe in StringRef -> StringId2 docs: ArcHandle::as_inner safety refactor: ProfilesDictionary error messages refactor: use thiserror to shorten code, even though it duplicates error messages Co-authored-by: levi.morrison <[email protected]>
1 parent b656c23 commit 3757554

File tree

9 files changed

+632
-3
lines changed

9 files changed

+632
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libdd-profiling-ffi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ hyper = { workspace = true}
5656
libc = "0.2"
5757
serde_json = { version = "1.0" }
5858
symbolizer-ffi = { path = "../symbolizer-ffi", optional = true, default-features = false }
59+
thiserror = "2"
5960
tokio-util = "0.7.1"
6061
datadog-ffe-ffi = { path = "../datadog-ffe-ffi", default-features = false, optional = true }

libdd-profiling-ffi/cbindgen.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ renaming_overrides_prefixing = true
106106
"CancellationToken" = "struct ddog_OpaqueCancellationToken"
107107
"Handle_TokioCancellationToken" = "ddog_CancellationToken"
108108

109+
"ArcHandle_ProfilesDictionary" = "ddog_prof_ProfilesDictionaryHandle"
110+
"ProfileStatus" = "ddog_prof_Status"
111+
109112
[export.mangle]
110113
rename_types = "PascalCase"
111114

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::profile_error::ProfileError;
5+
use crate::EmptyHandleError;
6+
use libdd_profiling::profiles::collections::Arc;
7+
use std::ptr::{null_mut, NonNull};
8+
9+
/// Opaque FFI handle to an `Arc<T>`'s inner `T`.
10+
///
11+
/// Safety rules for implementors/callers:
12+
/// - Do not create multiple owning `Arc<T>`s from the same raw pointer.
13+
/// - Always restore the original `Arc` with `into_raw` after any `from_raw`.
14+
/// - Use `as_inner()` to validate non-null before performing raw round-trips.
15+
///
16+
/// From Rust, use [`ArcHandle::try_clone`] to make a reference-counted copy.
17+
/// From the C FFI, the handle should probably be renamed to avoid generics
18+
/// bloat garbage, and a *_try_clone API should be provided.
19+
///
20+
/// Use [`ArcHandle::drop_resource`] to drop the resource and move this handle
21+
/// into the empty handle state, which is the default state.
22+
#[repr(transparent)]
23+
#[derive(Debug)]
24+
pub struct ArcHandle<T>(*mut T);
25+
26+
impl<T> Default for ArcHandle<T> {
27+
fn default() -> Self {
28+
Self(null_mut())
29+
}
30+
}
31+
32+
impl<T> ArcHandle<T> {
33+
/// Constructs a new handle by allocating an `ArcHandle<T>` and returning
34+
/// its inner pointer as a handle.
35+
///
36+
/// Returns OutOfMemory on allocation failure.
37+
pub fn new(value: T) -> Result<Self, ProfileError> {
38+
let arc = Arc::try_new(value)?;
39+
let ptr = Arc::into_raw(arc).as_ptr();
40+
Ok(Self(ptr))
41+
}
42+
43+
pub fn try_clone_into_arc(&self) -> Result<Arc<T>, ProfileError> {
44+
let clone = self.try_clone()?;
45+
// SAFETY: try_clone succeeded so it must not be null.
46+
let nn = unsafe { NonNull::new_unchecked(clone.0) };
47+
// SAFETY: validated that it isn't null, should otherwise be an Arc.
48+
Ok(unsafe { Arc::from_raw(nn) })
49+
}
50+
51+
#[inline]
52+
pub fn as_inner(&self) -> Result<&T, EmptyHandleError> {
53+
// SAFETY: If non-null, self.0 was created from Arc and remains valid,
54+
// at least as long as we can trust the C side to not do insane things.
55+
unsafe { self.0.as_ref() }.ok_or(EmptyHandleError)
56+
}
57+
58+
/// Tries to clone the resource this handle points to, and returns a new
59+
/// handle to it.
60+
pub fn try_clone(&self) -> Result<Self, ProfileError> {
61+
let nn = NonNull::new(self.0).ok_or(EmptyHandleError)?;
62+
// SAFETY: ArcHandle uses a pointer to T as its repr, and as long as
63+
// callers have upheld safety requirements elsewhere, including the
64+
// FFI, then there will be a valid object with refcount > 0.
65+
unsafe { Arc::try_increment_count(nn.as_ptr())? };
66+
Ok(Self(self.0))
67+
}
68+
69+
/// Drops the resource that this handle refers to. It will remain alive if
70+
/// there are other handles to the resource which were created by
71+
/// successful calls to try_clone. This handle will now be empty and
72+
/// operations on it will fail.
73+
pub fn drop_resource(&mut self) {
74+
// pointers aren't default until Rust 1.88.
75+
let ptr = core::mem::replace(&mut self.0, null_mut());
76+
if let Some(nn) = NonNull::new(ptr) {
77+
drop(unsafe { Arc::from_raw(nn) });
78+
}
79+
}
80+
}

libdd-profiling-ffi/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
#![cfg_attr(not(test), deny(clippy::todo))]
88
#![cfg_attr(not(test), deny(clippy::unimplemented))]
99

10+
mod arc_handle;
1011
mod exporter;
1112
mod profile_error;
1213
mod profile_status;
1314
mod profiles;
1415
mod string_storage;
1516

17+
pub use arc_handle::*;
1618
pub use profile_error::*;
1719
pub use profile_status::*;
1820

libdd-profiling-ffi/src/profiles/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,25 @@
33

44
mod datatypes;
55
mod interning_api;
6+
mod profiles_dictionary;
7+
mod utf8;
8+
9+
#[macro_export]
10+
macro_rules! ensure_non_null_out_parameter {
11+
($expr:expr) => {
12+
if $expr.is_null() {
13+
return $crate::ProfileStatus::from(c"null pointer used as out parameter");
14+
}
15+
};
16+
}
17+
18+
#[macro_export]
19+
macro_rules! ensure_non_null_insert {
20+
($expr:expr) => {
21+
if $expr.is_null() {
22+
return $crate::ProfileStatus::from(c"tried to insert a null pointer");
23+
}
24+
};
25+
}
26+
27+
pub(crate) use {ensure_non_null_insert, ensure_non_null_out_parameter};

0 commit comments

Comments
 (0)