Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
37f87dd
chore(native): bump libdatadog v35 → v37
mabdinur Jun 30, 2026
028f3b6
fix(native): remove libdd-http-client module dropped in libdatadog v36
mabdinur Jun 30, 2026
ed344e2
fix(native): remove enable_client_side_stats_obfuscation call in writer
mabdinur Jun 30, 2026
6d35021
fix(native): restore enable_client_side_stats_obfuscation via stats-o…
mabdinur Jun 30, 2026
89139de
fix(native): restore libdd-http-client module for v37 API
mabdinur Jun 30, 2026
d47d002
fix(ffe): map FlagConfigurationInvalid to PARSE_ERROR
bm1549 Jun 24, 2026
0b12d69
Merge branch 'main' into munir/bump-libdatadog-v37
mabdinur Jul 1, 2026
e68bfd0
Apply suggestions from code review
mabdinur Jul 2, 2026
07006b5
chore(native): restore Cargo.toml comment and dependency ordering
mabdinur Jul 2, 2026
4609325
fix(ffe): merge FlagConfigurationInvalid into ConfigurationParseError…
mabdinur Jul 2, 2026
894d1f3
fix(native): restore BlockingRuntime+ForkSafeRuntime imports in http_…
mabdinur Jul 2, 2026
3195507
fix(native): import SharedRuntime trait to resolve ForkSafeRuntime::n…
mabdinur Jul 2, 2026
71718d0
test(crashtracker): skip native extension crash test on CPython 3.14
mabdinur Jul 2, 2026
9a336c1
Merge remote-tracking branch 'origin/main' into munir/bump-libdatadog…
mabdinur Jul 6, 2026
17fb480
style: fix ruff line-length on skipif decorator in crashtracker test
mabdinur Jul 6, 2026
c000991
Apply suggestion from @mabdinur
mabdinur Jul 6, 2026
52fa5a9
fix(crashtracker): pin libdatadog to fix that excludes crashing threa…
mabdinur Jul 7, 2026
0b16da7
Revert "fix(crashtracker): pin libdatadog to fix that excludes crashi…
mabdinur Jul 7, 2026
f884eba
Apply suggestions from code review
mabdinur Jul 7, 2026
b441a95
gil guard
gyuheon0h Jul 8, 2026
64fe2e3
Merge branch 'main' into munir/bump-libdatadog-v37
mabdinur Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
923 changes: 505 additions & 418 deletions src/native/Cargo.lock

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions src/native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,32 @@ libc = { version = "0.2", optional = true }
rand = "0.9"
rustc-hash = "1.1"
serde = "1.0"
datadog-ffe = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", optional = true, version = "1.0.0", features = ["pyo3"] }
datadog-ffe = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", optional = true, version = "1.0.0", features = ["pyo3"] }
serde_json = "1.0"
datadog-remote-config = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-tracer-flare = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", optional = true }
libdd-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", optional = true }
libdd-library-config = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-log = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", features = [
libdd-remote-config = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-tracer-flare = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", optional = true }
libdd-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", optional = true }
libdd-library-config = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-log = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", features = [
"stats-obfuscation"
] }
libdd-http-client = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-shared-runtime = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-profiling-ffi = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", optional = true, features = [
libdd-http-client = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-shared-runtime = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
libdd-profiling-ffi = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", optional = true, features = [
"cbindgen",
] }
libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", optional = true }
libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0" }
libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", optional = true }
libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0" }
pyo3 = { version = "0.28", features = ["extension-module", "anyhow"] }
tracing = { version = "0.1", default-features = false }
pyo3-ffi = { version = "0.28", optional = true }

[build-dependencies]
pyo3-build-config = "0.28"
build_common = { git = "https://github.com/DataDog/libdatadog", rev = "v35.0.0", features = [
build_common = { git = "https://github.com/DataDog/libdatadog", rev = "v37.0.0", features = [
"cbindgen",
] }

Expand Down
17 changes: 17 additions & 0 deletions src/native/crashtracker/crashtracker_runtime_stacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ extern "C" {
fn fcntl(fd: c_int, cmd: c_int, arg: c_int) -> c_int;
}

/// Check whether the current thread holds the GIL.
/// This is signal-safe: it's just a thread-local read + comparison.
/// Returns false if the GIL is not held (e.g. during a ctypes foreign function call),
/// in which case it's unsafe to call any Python C API functions.
#[cfg(unix)]
unsafe fn gil_is_held() -> bool {
Comment thread
mabdinur marked this conversation as resolved.
pyo3_ffi::PyGILState_Check() != 0
}

/************************************************************
Emit runtime stacktrace as string using _Py_DumpTracebackThreads
************************************************************/
Expand Down Expand Up @@ -70,6 +79,11 @@ pub unsafe fn get_cached_dump_traceback_fn() -> Option<PyDumpTracebackThreadsFn>
unsafe fn dump_python_traceback_as_string(
emit_stacktrace_string: unsafe extern "C" fn(*const c_char),
) {
if !gil_is_held() {
emit_stacktrace_string(c"<python_runtime_stacktrace_unavailable>".as_ptr());
return;
}

// Use function linked during registration
let dump_fn = match get_cached_dump_traceback_fn() {
Some(func) => func,
Expand Down Expand Up @@ -160,6 +174,9 @@ const FRAME_FILE_CAP: usize = 512;

#[cfg(unix)]
unsafe fn capture_frames_via_python(emit_frame: unsafe extern "C" fn(&RuntimeStackFrame)) {
if !gil_is_held() {
return;
}
let current = pyo3_ffi::PyThreadState_Get();
if !current.is_null() {
let _ = collect_and_emit_frames_for_thread(current, emit_frame);
Expand Down
7 changes: 4 additions & 3 deletions src/native/data_pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use libdd_data_pipeline::trace_exporter::{
agent_response::AgentResponse, TelemetryConfig, TraceExporter, TraceExporterBuilder,
TraceExporterInputFormat, TraceExporterOutputFormat,
};
use libdd_shared_runtime::ForkSafeRuntime;
use pyo3::{exceptions::PyValueError, prelude::*, pybacked::PyBackedBytes};
use std::time::Duration;
mod agent_response;
Expand All @@ -16,11 +17,11 @@ use exceptions::TraceExporterErrorPy;
/// once `build` has been called the builder shouldn't be reused.
#[pyclass(name = "TraceExporterBuilder")]
pub struct TraceExporterBuilderPy {
builder: Option<TraceExporterBuilder>,
builder: Option<TraceExporterBuilder<ForkSafeRuntime>>,
}

impl TraceExporterBuilderPy {
fn try_as_mut(&mut self) -> PyResult<&mut TraceExporterBuilder> {
fn try_as_mut(&mut self) -> PyResult<&mut TraceExporterBuilder<ForkSafeRuntime>> {
self.builder
.as_mut()
.ok_or(PyValueError::new_err("Builder has already been consumed"))
Expand Down Expand Up @@ -222,7 +223,7 @@ impl TraceExporterBuilderPy {
/// A python object wrapping a [TraceExporter] instance
#[pyclass(name = "TraceExporter")]
pub struct TraceExporterPy {
inner: Option<TraceExporter<NativeCapabilities>>,
inner: Option<TraceExporter<NativeCapabilities, ForkSafeRuntime>>,
}

#[pymethods]
Expand Down
4 changes: 3 additions & 1 deletion src/native/ffe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ pub mod ffe {
ErrorCode::TypeMismatch,
format!("type mismatch, expected={expected:?}, found={found:?}"),
),
EvaluationError::ConfigurationParseError => {
EvaluationError::ConfigurationParseError
| EvaluationError::FlagConfigurationInvalid => {
ResolutionDetails::error(ErrorCode::ParseError, "configuration error")
}
EvaluationError::ConfigurationMissing => ResolutionDetails::error(
Expand Down Expand Up @@ -231,6 +232,7 @@ pub mod ffe {
AssignmentReason::TargetingMatch => Reason::TargetingMatch,
AssignmentReason::Split => Reason::Split,
AssignmentReason::Static => Reason::Static,
AssignmentReason::Default => Reason::Default,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/native/http_client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//! singleton.

use libdd_http_client::{HttpClient, HttpClientError, HttpMethod, HttpRequest, RetryConfig};
use libdd_shared_runtime::SharedRuntime;
use libdd_shared_runtime::{BlockingRuntime, ForkSafeRuntime};
#[cfg(not(unix))]
use pyo3::exceptions::PyValueError;
use pyo3::{prelude::*, pybacked::PyBackedBytes};
Expand All @@ -33,7 +33,7 @@ use crate::shared_runtime::SharedRuntimePy;
/// it to inject the shared runtime.
#[pyclass(name = "HTTPClient", subclass, frozen)]
pub struct HttpClientPy {
inner: (HttpClient, Arc<SharedRuntime>),
inner: (HttpClient, Arc<ForkSafeRuntime>),
// Request origin that relative paths are joined onto (e.g.
// "http://localhost:8126"; "http://localhost" for UDS). No trailing slash.
base: String,
Expand Down
8 changes: 4 additions & 4 deletions src/native/shared_runtime/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use libdd_shared_runtime::SharedRuntime;
use libdd_shared_runtime::{ForkSafeRuntime, SharedRuntime};
use pyo3::prelude::*;
use std::sync::Arc;
use std::time::Duration;
Expand All @@ -8,11 +8,11 @@ use exceptions::shared_runtime_error_to_pyerr;

#[pyclass(name = "SharedRuntime", subclass)]
pub struct SharedRuntimePy {
inner: Arc<SharedRuntime>,
inner: Arc<ForkSafeRuntime>,
}

impl SharedRuntimePy {
pub(crate) fn as_arc(&self) -> &Arc<SharedRuntime> {
pub(crate) fn as_arc(&self) -> &Arc<ForkSafeRuntime> {
&self.inner
}
}
Expand All @@ -21,7 +21,7 @@ impl SharedRuntimePy {
impl SharedRuntimePy {
#[new]
fn new() -> PyResult<Self> {
let inner = SharedRuntime::new().map_err(shared_runtime_error_to_pyerr)?;
let inner = ForkSafeRuntime::new().map_err(shared_runtime_error_to_pyerr)?;
Ok(Self {
inner: Arc::new(inner),
})
Expand Down
56 changes: 30 additions & 26 deletions src/native/tracer_flare.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use datadog_remote_config::{RemoteConfigData, RemoteConfigProduct};
use libdd_remote_config::config::{agent_config, agent_task};
use libdd_tracer_flare::{error::FlareError, FlareAction, TracerFlareManager};
use pyo3::{create_exception, exceptions::PyException, prelude::*, Bound, PyErr};

Expand Down Expand Up @@ -157,36 +157,40 @@ impl TracerFlareManagerPy {
fn handle_remote_config_data(&self, data: &[u8], product: &str) -> PyResult<FlareActionPy> {
let manager = &self.manager;

let product: RemoteConfigProduct = match product {
"AGENT_CONFIG" => RemoteConfigProduct::AgentConfig,
"AGENT_TASK" => RemoteConfigProduct::AgentTask,
_ => {
return Err(ParsingError::new_err(format!(
"Received unexpected tracer flare product type: {}",
product
)));
}
};

let config_data: RemoteConfigData = match RemoteConfigData::try_parse(product, data) {
Ok(data) => data,
Err(e) => {
match product {
"AGENT_CONFIG" => match agent_config::parse_json(data) {
Ok(config) => Ok(manager
.handle_remote_config_data(&config)
.map_err(|e| {
ParsingError::new_err(format!("Parsing error for AGENT_CONFIG: {}", e))
})?
.into()),
// AGENT_CONFIG has multiple payload shapes; e.g. configuration_order
// is valid JSON but not our schema. Valid-JSON parse failures return none_action()
// silently; truly malformed JSON propagates as ParsingError.
if product == RemoteConfigProduct::AgentConfig
&& serde_json::from_slice::<serde_json::Value>(data).is_ok()
{
return Ok(FlareActionPy::none_action());
Err(e) => {
if serde_json::from_slice::<serde_json::Value>(data).is_ok() {
Ok(FlareActionPy::none_action())
} else {
Err(ParsingError::new_err(format!("Parsing error: {}", e)))
}
}
return Err(ParsingError::new_err(format!("Parsing error: {}", e)));
},
"AGENT_TASK" => {
let task = agent_task::parse_json(data)
.map_err(|e| ParsingError::new_err(format!("Parsing error: {}", e)))?;
Ok(manager
.handle_remote_config_data(&task)
.map_err(|e| {
ParsingError::new_err(format!("Parsing error for AGENT_TASK: {}", e))
})?
.into())
}
};

Ok(manager
.handle_remote_config_data(&config_data)
.map_err(|e| ParsingError::new_err(format!("Parsing error for AGENT_CONFIG: {}", e)))?
.into())
_ => Err(ParsingError::new_err(format!(
"Received unexpected tracer flare product type: {}",
product
))),
}
}

/// Zips files from a directory and sends them to the agent.
Expand Down
1 change: 0 additions & 1 deletion tests/crashtracker/test_crashtracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,6 @@ def test_crashtracker_all_threads(run_python_code_in_subprocess):

@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only")
@pytest.mark.skipif(not shutil.which("g++"), reason="g++ required to compile the native extension")
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Runtime stacks are only supported on CPython >= 3.10")
Comment thread
mabdinur marked this conversation as resolved.
def test_crashtracker_native_extension_crash(run_python_code_in_subprocess):
import json

Expand Down
Loading