From 44a0728f7ac2760b077757bd380b6b1b7961d6bc Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Tue, 17 Feb 2026 18:19:47 +0100 Subject: [PATCH 01/23] chore: add rustix dependency Add an explicit rustix dependency (which was already pulled as a transitive dependency). Prep work for process context sharing. --- Cargo.lock | 1 + libdd-library-config/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 597e5c4290..f5e51d432b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3167,6 +3167,7 @@ dependencies = [ "rand 0.8.5", "rmp", "rmp-serde", + "rustix 1.1.3", "serde", "serde_yaml", "tempfile", diff --git a/libdd-library-config/Cargo.toml b/libdd-library-config/Cargo.toml index d1b136f3e2..94de474a32 100644 --- a/libdd-library-config/Cargo.toml +++ b/libdd-library-config/Cargo.toml @@ -22,6 +22,7 @@ anyhow = "1.0" rand = "0.8.3" rmp = "0.8.14" rmp-serde = "1.3.0" +rustix = { version = "1.1.3", features = ["param", "mm"] } [dev-dependencies] tempfile = { version = "3.3" } From 9acff8799ec3981eccba20a4ccd46432525eaab7 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Tue, 17 Feb 2026 18:21:54 +0100 Subject: [PATCH 02/23] feat: add process context publication --- libdd-library-config/Cargo.toml | 2 +- libdd-library-config/src/lib.rs | 1 + libdd-library-config/src/process_context.rs | 364 ++++++++++++++++++++ 3 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 libdd-library-config/src/process_context.rs diff --git a/libdd-library-config/Cargo.toml b/libdd-library-config/Cargo.toml index 94de474a32..7c768070c1 100644 --- a/libdd-library-config/Cargo.toml +++ b/libdd-library-config/Cargo.toml @@ -22,7 +22,7 @@ anyhow = "1.0" rand = "0.8.3" rmp = "0.8.14" rmp-serde = "1.3.0" -rustix = { version = "1.1.3", features = ["param", "mm"] } +rustix = { version = "1.1.3", features = ["param", "mm", "process"] } [dev-dependencies] tempfile = { version = "3.3" } diff --git a/libdd-library-config/src/lib.rs b/libdd-library-config/src/lib.rs index 6374ba650f..3400bf1bf5 100644 --- a/libdd-library-config/src/lib.rs +++ b/libdd-library-config/src/lib.rs @@ -1,5 +1,6 @@ // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +pub mod process_context; pub mod tracer_metadata; use std::borrow::Cow; diff --git a/libdd-library-config/src/process_context.rs b/libdd-library-config/src/process_context.rs new file mode 100644 index 0000000000..12a6eae195 --- /dev/null +++ b/libdd-library-config/src/process_context.rs @@ -0,0 +1,364 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of the publisher part of the [process sharing protocol](https://github.com/open-telemetry/opentelemetry-specification/pull/4719) +//! +//! # A note on race conditions +//! +//! Process context sharing implies concurrently writing to a memory area that another process +//! might be actively reading. However, reading isn't done as direct memory accesses but go through +//! the OS, so the Rust definition of race conditions doesn't really apply. +//! +//! Still, we typically want to avoid the compiler and the hardware to re-order the write to the +//! signature (which should be last according to the specification) with the writes to other fields +//! of the header. +//! +//! To do so, we implement synchronization during publication _as if the reader were another thread +//! of this program_, using atomics. + +/// Current version of the process context format +pub const PROCESS_CTX_VERSION: u32 = 2; +/// Signature bytes for identifying process context mappings +pub const SIGNATURE: &[u8; 8] = b"OTEL_CTX"; +/// The discoverable name of the memory mapping. +pub const MAPPING_NAME: &str = "OTEL_CTX"; + +#[cfg(target_os = "linux")] +#[cfg(target_has_atomic = "64")] +pub mod linux { + use super::{MAPPING_NAME, PROCESS_CTX_VERSION, SIGNATURE}; + + use std::{ + ffi::c_void, + mem::ManuallyDrop, + os::fd::{AsFd as _, OwnedFd}, + ptr, + sync::{ + atomic::{AtomicU64, Ordering}, + Mutex, MutexGuard, + }, + time::{SystemTime, UNIX_EPOCH}, + }; + + use anyhow::Context; + + use rustix::{ + fs::{ftruncate, memfd_create, MemfdFlags}, + mm::{madvise, mmap, mmap_anonymous, munmap, Advice, MapFlags, ProtFlags}, + param::page_size, + process::{getpid, set_virtual_memory_region_name, Pid}, + }; + + /// The header structure written at the start of the mapping. This must match the C + /// layout of the specification. + /// + /// # Atomic accesses + /// + /// The publishing protocol requires some form of synchronization. Using fences or any non-OS + /// based synchronization requires the use of atomics to have any effect (see [Mandatory + /// atomic](https://doc.rust-lang.org/std/sync/atomic/fn.fence.html#mandatory-atomic)) + /// + /// We use `signature` as a release notification for publication, and `published_at_ns` for + /// updates. Ideally, those should be two `AtomicU64`, but this isn't compatible with + /// `#[repr(C, packed)]`, since `AtomicU64` can't be used in a packed structure for alignment + /// reason (what's more, their alignment might be bigger than the one of `u64` on some + /// platforms). + /// + /// In practice, given the page size and the layout of `MappingHeader`, the alignment should + /// match (we statically test for it anyway). We can then use [`AtomicU64::from_ptr`] to create + /// an atomic view of those fields when synchronization is needed. + #[repr(C, packed)] + struct MappingHeader { + signature: [u8; 8], + version: u32, + payload_size: u32, + published_at_ns: u64, + payload_ptr: *const u8, + } + + /// The shared memory mapped area to publish the context to. The memory region is owned by a + /// [MemMapping] instance and is automatically unmapped upon drop. + /// + /// # Safety + /// + /// The following invariants MUST always hold for safety and are guaranteed by [MemMapping]: + /// - `start` is non-null, is coming from a previous call to `mmap` with a size value of + /// [mapping_size] and hasn't been unmmaped since. + /// - once `self` has been dropped, no memory access must be performed on the memory previously + /// pointed to by `start`. + struct MemMapping { + start_addr: *mut c_void, + /// The file descriptor, if the mapping was successfully created from `memfd`. + fd: Option, + } + + // Safety: MemMapping represents ownership over the mapped region. It never leaks or + // share the internal pointer. It's also safe to drop (`munmap`) from a different thread. + unsafe impl Send for MemMapping {} + + /// The global instance of the context for the current process. + /// + /// We need a mutex to put the handle in a static and avoid bothering the users of this API + /// with storing the handle, but we don't expect this mutex to actually be contended. Ideally a + /// single thread should handle context updates, even if it's not strictly required. + static PROCESS_CONTEXT_HANDLER: Mutex> = Mutex::new(None); + + impl MemMapping { + /// Creates a suitable memory mapping for the context protocol to be published. + /// + /// `memfd` is the preferred method, but this function fallbacks to an anonymous mapping on + /// old kernels that don't support `memfd` (or if `memfd` failed). + fn new() -> anyhow::Result { + let size = mapping_size(); + + memfd_create( + MAPPING_NAME, + MemfdFlags::CLOEXEC | MemfdFlags::NOEXEC_SEAL | MemfdFlags::ALLOW_SEALING, + ) + .or_else(|_| memfd_create(MAPPING_NAME, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)) + .and_then(|fd| { + ftruncate(fd.as_fd(), mapping_size() as u64)?; + // Safety: we pass a null pointer to mmap which is unconditionally ok + let start_addr = unsafe { + mmap( + ptr::null_mut(), + size, + ProtFlags::WRITE | ProtFlags::READ, + MapFlags::SHARED, + fd.as_fd(), + 0, + )? + }; + + Ok(MemMapping { + start_addr, + fd: Some(fd), + }) + }) + // If any previous step failed, we fallback to an anonymous mapping + .or_else(|_| { + // Safety: we pass a null pointer to mmap, no precondition to uphold + let start_addr = unsafe { + mmap_anonymous( + ptr::null_mut(), + size, + ProtFlags::WRITE | ProtFlags::READ, + MapFlags::PRIVATE, + ) + .context( + "Couldn't create a memfd or anonymous mmapped region for process context publication", + )? + }; + + Ok(MemMapping { start_addr, fd: None }) + }) + } + + /// Makes this mapping discoverable by giving it a name. This is not required for a + /// memfd-backed mapping. + fn set_name(&mut self) -> anyhow::Result<()> { + // Safety: the invariants of `MemMapping` ensures that `start` is non null and comes + // from a previous call to `mmap` of size `mapping_size()` + set_virtual_memory_region_name( + unsafe { std::slice::from_raw_parts(self.start_addr as *const u8, mapping_size()) }, + Some( + std::ffi::CString::new(MAPPING_NAME) + .context("unexpected null byte in process context mapping name")? + .as_c_str(), + ), + )?; + Ok(()) + } + + /// Unmaps the underlying memory region and close the memfd file descriptor, if set. This + /// has same effect as dropping `self`, but propagates potential errors. + fn free(mut self) -> anyhow::Result<()> { + // Safety: We put `self` in a `ManuallyDrop`, which prevents drop and future calls to + // `free()`. + unsafe { + self.unmap()?; + } + + // Ensure `fd` is dropped and thus closed + self.fd = None; + // Prevent `Self::drop` from being called + let _ = ManuallyDrop::new(self); + + Ok(()) + } + + /// Unmaps the underlying memory region. For internal use only; prefer `free()` or `drop()`. + /// + /// # Safety + /// + /// This method must only be called once. After calling `unmap()`, no other method of + /// `MemMapping` must be ever called on `self` again, including `unmap()` and `drop()`. + /// + /// Practically, `self` must be put in a `ManuallyDrop` wrapper and forgotten. + unsafe fn unmap(&mut self) -> anyhow::Result<()> { + unsafe { + munmap(self.start_addr, mapping_size()).map_err(|errno| { + anyhow::anyhow!( + "munmap failed when freeing the process context with error {errno}" + ) + }) + } + } + } + + impl Drop for MemMapping { + fn drop(&mut self) { + // Safety: `self` is being dropped + let _ = unsafe { self.unmap() }; + } + } + + /// Handle for future updates of a published process context. + #[cfg(target_os = "linux")] + struct ProcessContextHandle { + mapping: MemMapping, + /// Once published, and until the next update is complete, the backing allocation of + /// `payload` might be read by external processes and thus most not move (e.g. by resizing + /// or drop). + #[allow(unused)] + payload: Vec, + #[allow(unused)] + publisher_pid: Pid, + } + + impl ProcessContextHandle { + /// Initial publication of the process context. Creates an appropriate memory mapping. + fn publish(payload: Vec) -> anyhow::Result { + let mut mapping = MemMapping::new()?; + let size = mapping_size(); + + // Checks that the layout allow us to access `signature` and `published_at_ns` as + // atomics u64. Page size is at minimum 4KB and will be always 8 bytes aligned even on + // exotic platforms. The respective offsets of `signature` and `published_at_ns` are + // 0 and 8 bytes, so it suffices for `AtomicU64` to require an alignment of at most 8 + // (which is the expected alignment anyway). + // + // Note that `align_of` is a `const fn`, so this is in fact a compile-time check and + // will be optimized away, hence the `allow(unreachable_code)`. + #[allow(unreachable_code)] + if std::mem::align_of::() > 8 { + return Err(anyhow::anyhow!("alignment constraints forbid the use of atomics for publishing the protocol context")); + } + + // Safety: the invariants of MemMapping ensures `start_addr` is not null and comes + // from a previous call to `mmap` + unsafe { madvise(mapping.start_addr, size, Advice::LinuxDontFork) } + .context("madvise MADVISE_DONTFORK failed")?; + + let published_at_ns = time_now_ns(); + + if published_at_ns == 0 { + return Err(anyhow::anyhow!( + "failed to get current time for process context publication" + )); + } + + let header = mapping.start_addr as *mut MappingHeader; + + unsafe { + // Safety: MappingHeader is packed, thus have no alignment requirement. It points + // to a freshly mmaped region which is valid for writing at least PAGE_SIZE bytes, + // which is greater than the size of MappingHeader. + ptr::write( + header, + MappingHeader { + // signature will be set atomically at last + signature: [0; 8], + version: PROCESS_CTX_VERSION, + payload_size: payload + .len() + .try_into() + .context("payload size overflowed")?, + published_at_ns, + payload_ptr: payload.as_ptr(), + }, + ); + // Signature is set last, which means that all the previous stores happens-before it + // (program order on a given single thread). Any fence or atomic load from the + // reader side which loads the completed signature with at least + // `Acquire` ordering will create a happens-before relationship with + // `signature`, ensuring the header is seen as fully initialized on + // their side. + AtomicU64::from_ptr((*header).signature.as_mut_ptr().cast::()) + // To avoid shuffling bytes, we must use the native endianness + .store(u64::from_ne_bytes(*SIGNATURE), Ordering::Release); + } + + // For anonymous mappings, try to name it (optional, may fail on older kernels). + // `memfd` mappings don't need this - the name shows in /proc/pid/maps automatically + if mapping.fd.is_none() { + let _ = mapping.set_name(); + } + + Ok(ProcessContextHandle { + mapping, + payload, + publisher_pid: getpid(), + }) + } + + /// Updates the context after initial publication. Currently unimplemented (always returns + /// `Err`). + fn update(&mut self) -> anyhow::Result<()> { + Err(anyhow::anyhow!( + "process context update isn't implemented yet" + )) + } + } + + fn time_now_ns() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .ok() + .and_then(|d| u64::try_from(d.as_nanos()).ok()) + .unwrap_or(0) + } + + fn mapping_size() -> usize { + page_size() * 2 + } + + /// Locks the context handle. Returns a uniform error if the lock has been poisoned. + fn lock_context_handle() -> anyhow::Result>> { + PROCESS_CONTEXT_HANDLER.lock().map_err(|_| { + anyhow::anyhow!("a thread panicked while operating on the process context handler") + }) + } + + /// Publishes or updates the process context for it to be visible by external readers. + /// + /// If this is the first publication, or if [unpublish] has been called last, this will follow + /// the Publish protocol of the process context specification. + /// + /// Otherwise, the context is updated following the Update protocol. + pub fn publish(payload: Vec) -> anyhow::Result<()> { + let mut guard = lock_context_handle()?; + + match &mut *guard { + Some(handler) => handler.update(), + None => { + *guard = Some(ProcessContextHandle::publish(payload)?); + Ok(()) + } + } + } + + /// Unmaps the region used to share the process context and close the associated file + /// descriptor, if any. If no context has ever been published, this is no-op. + /// + /// A call to [publish] following an [unpublish] will create a new mapping. + pub fn unpublish() -> anyhow::Result<()> { + let mut guard = lock_context_handle()?; + + if let Some(ProcessContextHandle { mapping, .. }) = guard.take() { + mapping.free()?; + } + + Ok(()) + } +} From 93fb32fd073189c3abf5ace2c8893001aa3d8fe4 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Fri, 20 Feb 2026 15:46:33 +0100 Subject: [PATCH 03/23] test: add reading test for process context --- libdd-library-config/src/process_context.rs | 157 ++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/libdd-library-config/src/process_context.rs b/libdd-library-config/src/process_context.rs index 12a6eae195..2b9688f74e 100644 --- a/libdd-library-config/src/process_context.rs +++ b/libdd-library-config/src/process_context.rs @@ -361,4 +361,161 @@ pub mod linux { Ok(()) } + + #[cfg(test)] + mod tests { + use super::MappingHeader; + use anyhow::ensure; + use std::{ + fs::File, + io::{BufRead, BufReader}, + sync::atomic::{AtomicU64, Ordering}, + }; + + /// Parses the start address from a /proc/self/maps line + fn parse_mapping_start(line: &str) -> Option { + usize::from_str_radix(line.split('-').next()?, 16).ok() + } + + /// Parses start and end addresses from a /proc/self/maps line + fn parse_mapping_range(line: &str) -> Option<(usize, usize)> { + let mut parts = line.split_whitespace(); + let range = parts.next()?; + let mut addrs = range.split('-'); + let start = usize::from_str_radix(addrs.next()?, 16).ok()?; + let end = usize::from_str_radix(addrs.next()?, 16).ok()?; + + Some((start, end)) + } + + /// Checks if a /proc/self/maps line is a potential OTEL_CTX mapping + /// + /// Accepts both read-only (" r--p ") and read-write (" rw-p "/" rw-s ") permissions + /// for backwards compatibility (old writers used read-only, new writers use read-write). + /// memfd mappings use MAP_SHARED so they show as " rw-s ". + /// + /// Also accepts both 1-page (new) and 2-page (old) mapping sizes. + fn is_otel_mapping_candidate(line: &str) -> bool { + // Accept read-write private (anon), and read-write shared (memfd) + if !(line.contains(" rw-p ") || line.contains(" rw-s ")) { + return false; + } + + // Check that the size matches + parse_mapping_range(line) + .map(|(start, end)| end.saturating_sub(start) == super::mapping_size()) + .unwrap_or(false) + } + + /// Checks if a mapping line refers to the OTEL_CTX mapping by name + /// + /// Handles both anonymous naming (`[anon:OTEL_CTX]`) and memfd naming + /// (`/memfd:OTEL_CTX` which may have ` (deleted)` suffix). + fn is_named_otel_mapping(line: &str) -> bool { + let trimmed = line.trim_end(); + trimmed.ends_with("[anon:OTEL_CTX]") + || trimmed.contains("/memfd:OTEL_CTX") + || trimmed.contains("memfd:OTEL_CTX") + } + + /// Checks if a mapping line refers to the OTEL_CTX memfd mapping + fn is_memfd_otel_mapping(line: &str) -> bool { + line.contains("/memfd:OTEL_CTX") + } + + /// Reads the signature from a memory address to verify it's an OTEL_CTX mapping. This also + /// establish proper synchronization/memory ordering through atomics since the reader is + /// the same process in this test setup. + fn verify_signature_at(addr: usize) -> bool { + let ptr: *mut u64 = std::ptr::with_exposed_provenance_mut(addr); + // Safety: We're reading from our own process memory at an address + // we found in /proc/self/maps. This should be safe as long as the + // mapping exists and has read permissions. + // + // The atomic alignment constraints are checked during publication. + let signature = unsafe { AtomicU64::from_ptr(ptr).load(Ordering::Acquire) }; + &signature.to_ne_bytes() == super::SIGNATURE + } + + /// Find the OTEL_CTX mapping in /proc/self/maps + fn find_otel_mapping() -> anyhow::Result { + let file = File::open("/proc/self/maps")?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + + if !is_otel_mapping_candidate(&line) { + continue; + } + + if is_named_otel_mapping(&line) { + if let Some(addr) = parse_mapping_start(&line) { + return Ok(addr); + } + } + + if is_memfd_otel_mapping(&line) { + if let Some(addr) = parse_mapping_start(&line) { + return Ok(addr); + } + } + + // For unnamed mappings, verify by reading the signature + if let Some(addr) = parse_mapping_start(&line) { + if verify_signature_at(addr) { + return Ok(addr); + } + } + } + + Err(anyhow::anyhow!( + "couldn't find the mapping of the process context" + )) + } + + /// Read the process context from the current process. + /// + /// This searches `/proc/self/maps` for an OTEL_CTX mapping and decodes its contents. + pub fn read_process_context() -> anyhow::Result { + let mapping_addr = find_otel_mapping()?; + let header_ptr = mapping_addr as *const MappingHeader; + + // Note: verifying the signature ensures proper synchronization + ensure!( + verify_signature_at(mapping_addr), + "verification of the signature failed" + ); + + // Safety: we found this address in /proc/self/maps and verified the signature + Ok(unsafe { std::ptr::read(header_ptr) }) + } + + #[test] + #[cfg_attr(miri, ignore)] + fn publish_then_read_context() { + let payload = "example process context payload"; + + super::publish(payload.as_bytes().to_vec()) + .expect("couldn't publish the process context"); + let header = read_process_context().expect("couldn't read back the process contex"); + // Safety: the published context must have put valid bytes of size payload_size in the + // context if the signature check succeded. + let read_payload = unsafe { + std::slice::from_raw_parts(header.payload_ptr, header.payload_size as usize) + }; + + assert!(header.signature == *super::SIGNATURE, "wrong signature"); + assert!( + header.version == super::PROCESS_CTX_VERSION, + "wrong context version" + ); + assert!( + header.payload_size == payload.len() as u32, + "wrong payload size" + ); + assert!(header.published_at_ns > 0, "published_at_ns is zero"); + assert!(read_payload == payload.as_bytes(), "payload mismatch"); + } + } } From 63e41f7857f984e54f3d5f452250c0afa884edff Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 13:28:19 +0100 Subject: [PATCH 04/23] chore: review suggestion for otel process ctxt --- libdd-library-config/src/lib.rs | 2 +- ...rocess_context.rs => otel_process_ctxt.rs} | 140 +++++------------- 2 files changed, 39 insertions(+), 103 deletions(-) rename libdd-library-config/src/{process_context.rs => otel_process_ctxt.rs} (79%) diff --git a/libdd-library-config/src/lib.rs b/libdd-library-config/src/lib.rs index 3400bf1bf5..ce07caea62 100644 --- a/libdd-library-config/src/lib.rs +++ b/libdd-library-config/src/lib.rs @@ -1,6 +1,6 @@ // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -pub mod process_context; +pub mod otel_process_ctxt; pub mod tracer_metadata; use std::borrow::Cow; diff --git a/libdd-library-config/src/process_context.rs b/libdd-library-config/src/otel_process_ctxt.rs similarity index 79% rename from libdd-library-config/src/process_context.rs rename to libdd-library-config/src/otel_process_ctxt.rs index 2b9688f74e..35c79fe131 100644 --- a/libdd-library-config/src/process_context.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -1,20 +1,13 @@ // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -//! Implementation of the publisher part of the [process sharing protocol](https://github.com/open-telemetry/opentelemetry-specification/pull/4719) +//! Implementation of the publisher part of the [OTEL process context](https://github.com/open-telemetry/opentelemetry-specification/pull/4719) //! //! # A note on race conditions //! //! Process context sharing implies concurrently writing to a memory area that another process //! might be actively reading. However, reading isn't done as direct memory accesses but go through //! the OS, so the Rust definition of race conditions doesn't really apply. -//! -//! Still, we typically want to avoid the compiler and the hardware to re-order the write to the -//! signature (which should be last according to the specification) with the writes to other fields -//! of the header. -//! -//! To do so, we implement synchronization during publication _as if the reader were another thread -//! of this program_, using atomics. /// Current version of the process context format pub const PROCESS_CTX_VERSION: u32 = 2; @@ -31,10 +24,10 @@ pub mod linux { use std::{ ffi::c_void, mem::ManuallyDrop, - os::fd::{AsFd as _, OwnedFd}, + os::fd::AsFd as _, ptr, sync::{ - atomic::{AtomicU64, Ordering}, + atomic::{fence, AtomicU64, Ordering}, Mutex, MutexGuard, }, time::{SystemTime, UNIX_EPOCH}, @@ -88,8 +81,6 @@ pub mod linux { /// pointed to by `start`. struct MemMapping { start_addr: *mut c_void, - /// The file descriptor, if the mapping was successfully created from `memfd`. - fd: Option, } // Safety: MemMapping represents ownership over the mapped region. It never leaks or @@ -124,15 +115,15 @@ pub mod linux { ptr::null_mut(), size, ProtFlags::WRITE | ProtFlags::READ, - MapFlags::SHARED, + MapFlags::PRIVATE, fd.as_fd(), 0, )? }; + // We (implicitly) close the file descriptor right away, but this ok Ok(MemMapping { start_addr, - fd: Some(fd), }) }) // If any previous step failed, we fallback to an anonymous mapping @@ -150,12 +141,11 @@ pub mod linux { )? }; - Ok(MemMapping { start_addr, fd: None }) + Ok(MemMapping { start_addr }) }) } - /// Makes this mapping discoverable by giving it a name. This is not required for a - /// memfd-backed mapping. + /// Makes this mapping discoverable by giving it a name. fn set_name(&mut self) -> anyhow::Result<()> { // Safety: the invariants of `MemMapping` ensures that `start` is non null and comes // from a previous call to `mmap` of size `mapping_size()` @@ -179,8 +169,6 @@ pub mod linux { self.unmap()?; } - // Ensure `fd` is dropped and thus closed - self.fd = None; // Prevent `Self::drop` from being called let _ = ManuallyDrop::new(self); @@ -214,7 +202,6 @@ pub mod linux { } /// Handle for future updates of a published process context. - #[cfg(target_os = "linux")] struct ProcessContextHandle { mapping: MemMapping, /// Once published, and until the next update is complete, the backing allocation of @@ -250,14 +237,9 @@ pub mod linux { unsafe { madvise(mapping.start_addr, size, Advice::LinuxDontFork) } .context("madvise MADVISE_DONTFORK failed")?; - let published_at_ns = time_now_ns(); - - if published_at_ns == 0 { - return Err(anyhow::anyhow!( - "failed to get current time for process context publication" - )); - } - + let published_at_ns = time_now_ns().ok_or_else(|| { + anyhow::anyhow!("fail to get current time for process context publication") + })?; let header = mapping.start_addr as *mut MappingHeader; unsafe { @@ -278,22 +260,19 @@ pub mod linux { payload_ptr: payload.as_ptr(), }, ); - // Signature is set last, which means that all the previous stores happens-before it - // (program order on a given single thread). Any fence or atomic load from the - // reader side which loads the completed signature with at least - // `Acquire` ordering will create a happens-before relationship with - // `signature`, ensuring the header is seen as fully initialized on - // their side. + // We typically want to avoid the compiler and the hardware to re-order the write to + // the signature (which should be last according to the + // specification) with the writes to other fields of the header. + // + // To do so, we implement synchronization during publication _as if the reader were + // another thread of this program_, using atomics and fences. AtomicU64::from_ptr((*header).signature.as_mut_ptr().cast::()) // To avoid shuffling bytes, we must use the native endianness .store(u64::from_ne_bytes(*SIGNATURE), Ordering::Release); + fence(Ordering::SeqCst); } - // For anonymous mappings, try to name it (optional, may fail on older kernels). - // `memfd` mappings don't need this - the name shows in /proc/pid/maps automatically - if mapping.fd.is_none() { - let _ = mapping.set_name(); - } + let _ = mapping.set_name(); Ok(ProcessContextHandle { mapping, @@ -311,16 +290,17 @@ pub mod linux { } } - fn time_now_ns() -> u64 { + // Rustix's page_size caches the value in a static atomic, so it's ok to call mapping_size() + // repeatedly; it won't result in a syscall each time. + fn mapping_size() -> usize { + page_size() + } + + fn time_now_ns() -> Option { SystemTime::now() .duration_since(UNIX_EPOCH) .ok() .and_then(|d| u64::try_from(d.as_nanos()).ok()) - .unwrap_or(0) - } - - fn mapping_size() -> usize { - page_size() * 2 } /// Locks the context handle. Returns a uniform error if the lock has been poisoned. @@ -369,7 +349,7 @@ pub mod linux { use std::{ fs::File, io::{BufRead, BufReader}, - sync::atomic::{AtomicU64, Ordering}, + sync::atomic::{fence, AtomicU64, Ordering}, }; /// Parses the start address from a /proc/self/maps line @@ -377,50 +357,22 @@ pub mod linux { usize::from_str_radix(line.split('-').next()?, 16).ok() } - /// Parses start and end addresses from a /proc/self/maps line - fn parse_mapping_range(line: &str) -> Option<(usize, usize)> { - let mut parts = line.split_whitespace(); - let range = parts.next()?; - let mut addrs = range.split('-'); - let start = usize::from_str_radix(addrs.next()?, 16).ok()?; - let end = usize::from_str_radix(addrs.next()?, 16).ok()?; - - Some((start, end)) - } - - /// Checks if a /proc/self/maps line is a potential OTEL_CTX mapping - /// - /// Accepts both read-only (" r--p ") and read-write (" rw-p "/" rw-s ") permissions - /// for backwards compatibility (old writers used read-only, new writers use read-write). - /// memfd mappings use MAP_SHARED so they show as " rw-s ". - /// - /// Also accepts both 1-page (new) and 2-page (old) mapping sizes. - fn is_otel_mapping_candidate(line: &str) -> bool { - // Accept read-write private (anon), and read-write shared (memfd) - if !(line.contains(" rw-p ") || line.contains(" rw-s ")) { - return false; - } - - // Check that the size matches - parse_mapping_range(line) - .map(|(start, end)| end.saturating_sub(start) == super::mapping_size()) - .unwrap_or(false) - } - - /// Checks if a mapping line refers to the OTEL_CTX mapping by name + /// Checks if a mapping line refers to the OTEL_CTX mapping. /// /// Handles both anonymous naming (`[anon:OTEL_CTX]`) and memfd naming /// (`/memfd:OTEL_CTX` which may have ` (deleted)` suffix). fn is_named_otel_mapping(line: &str) -> bool { let trimmed = line.trim_end(); - trimmed.ends_with("[anon:OTEL_CTX]") - || trimmed.contains("/memfd:OTEL_CTX") - || trimmed.contains("memfd:OTEL_CTX") - } - /// Checks if a mapping line refers to the OTEL_CTX memfd mapping - fn is_memfd_otel_mapping(line: &str) -> bool { - line.contains("/memfd:OTEL_CTX") + // The name of the mapping is the 6th column. The separator changes (both ' ' and '\t') + // but `split_whitespace()` takes care of that. + let Some(name) = trimmed.split_whitespace().skip(5).next() else { + return false; + }; + + name.starts_with("/memfd:OTEL_CTX") + || name.starts_with("[anon_shmem:OTEL_CTX]") + || name.starts_with("[anon:OTEL_CTXT]") } /// Reads the signature from a memory address to verify it's an OTEL_CTX mapping. This also @@ -433,7 +385,8 @@ pub mod linux { // mapping exists and has read permissions. // // The atomic alignment constraints are checked during publication. - let signature = unsafe { AtomicU64::from_ptr(ptr).load(Ordering::Acquire) }; + let signature = unsafe { AtomicU64::from_ptr(ptr).load(Ordering::Relaxed) }; + fence(Ordering::SeqCst); &signature.to_ne_bytes() == super::SIGNATURE } @@ -445,28 +398,11 @@ pub mod linux { for line in reader.lines() { let line = line?; - if !is_otel_mapping_candidate(&line) { - continue; - } - if is_named_otel_mapping(&line) { if let Some(addr) = parse_mapping_start(&line) { return Ok(addr); } } - - if is_memfd_otel_mapping(&line) { - if let Some(addr) = parse_mapping_start(&line) { - return Ok(addr); - } - } - - // For unnamed mappings, verify by reading the signature - if let Some(addr) = parse_mapping_start(&line) { - if verify_signature_at(addr) { - return Ok(addr); - } - } } Err(anyhow::anyhow!( From 0a3ba1f31844bdf08e7d1610327690423503a7e6 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 13:37:28 +0100 Subject: [PATCH 05/23] chore: fix clippy warning --- libdd-library-config/src/otel_process_ctxt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctxt.rs index 35c79fe131..953955ef15 100644 --- a/libdd-library-config/src/otel_process_ctxt.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -366,7 +366,7 @@ pub mod linux { // The name of the mapping is the 6th column. The separator changes (both ' ' and '\t') // but `split_whitespace()` takes care of that. - let Some(name) = trimmed.split_whitespace().skip(5).next() else { + let Some(name) = trimmed.split_whitespace().nth(5) else { return false; }; From f28505988f9d0a73db671a754f270530357450e1 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 13:46:34 +0100 Subject: [PATCH 06/23] chore: add missing feature to rustix dep --- libdd-library-config/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-library-config/Cargo.toml b/libdd-library-config/Cargo.toml index 7c768070c1..2956212be4 100644 --- a/libdd-library-config/Cargo.toml +++ b/libdd-library-config/Cargo.toml @@ -22,7 +22,7 @@ anyhow = "1.0" rand = "0.8.3" rmp = "0.8.14" rmp-serde = "1.3.0" -rustix = { version = "1.1.3", features = ["param", "mm", "process"] } +rustix = { version = "1.1.3", features = ["param", "mm", "process", "fs"] } [dev-dependencies] tempfile = { version = "3.3" } From c8cb612393e5167824ad1695f652ff4f9f0d9420 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 16:09:40 +0100 Subject: [PATCH 07/23] test: fix process context sharing test The wrong mapping name was used for discovery. Co-authored-by: Scott Gerring --- libdd-library-config/src/otel_process_ctxt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctxt.rs index 953955ef15..6f79623537 100644 --- a/libdd-library-config/src/otel_process_ctxt.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -372,7 +372,7 @@ pub mod linux { name.starts_with("/memfd:OTEL_CTX") || name.starts_with("[anon_shmem:OTEL_CTX]") - || name.starts_with("[anon:OTEL_CTXT]") + || name.starts_with("[anon:OTEL_CTX]") } /// Reads the signature from a memory address to verify it's an OTEL_CTX mapping. This also From e4a7e869b1104b32e8774dc51a3e1fabf1e193df Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 16:12:01 +0100 Subject: [PATCH 08/23] test: free mapping in otel process ctxt test --- libdd-library-config/src/otel_process_ctxt.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctxt.rs index 6f79623537..6e3ed100e5 100644 --- a/libdd-library-config/src/otel_process_ctxt.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -452,6 +452,8 @@ pub mod linux { ); assert!(header.published_at_ns > 0, "published_at_ns is zero"); assert!(read_payload == payload.as_bytes(), "payload mismatch"); + + super::unpublish().expect("couldn't unpublish the context"); } } } From 63bfe33ecdce0e0a48e7d0f66052535fb636dc58 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 16:13:35 +0100 Subject: [PATCH 09/23] chore: guard rustix dependency to be linux-only --- libdd-library-config/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-library-config/Cargo.toml b/libdd-library-config/Cargo.toml index 2956212be4..2af7f2f373 100644 --- a/libdd-library-config/Cargo.toml +++ b/libdd-library-config/Cargo.toml @@ -22,10 +22,10 @@ anyhow = "1.0" rand = "0.8.3" rmp = "0.8.14" rmp-serde = "1.3.0" -rustix = { version = "1.1.3", features = ["param", "mm", "process", "fs"] } [dev-dependencies] tempfile = { version = "3.3" } [target.'cfg(unix)'.dependencies] memfd = { version = "0.6" } +rustix = { version = "1.1.3", features = ["param", "mm", "process", "fs"] } From 11ff38d6e39ea9ff2741608e2f855c5670631249 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 16:20:14 +0100 Subject: [PATCH 10/23] chore: remove unused field in otel process ctxt --- libdd-library-config/src/otel_process_ctxt.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctxt.rs index 6e3ed100e5..0aff947cc8 100644 --- a/libdd-library-config/src/otel_process_ctxt.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -39,7 +39,7 @@ pub mod linux { fs::{ftruncate, memfd_create, MemfdFlags}, mm::{madvise, mmap, mmap_anonymous, munmap, Advice, MapFlags, ProtFlags}, param::page_size, - process::{getpid, set_virtual_memory_region_name, Pid}, + process::set_virtual_memory_region_name, }; /// The header structure written at the start of the mapping. This must match the C @@ -209,8 +209,6 @@ pub mod linux { /// or drop). #[allow(unused)] payload: Vec, - #[allow(unused)] - publisher_pid: Pid, } impl ProcessContextHandle { @@ -274,11 +272,7 @@ pub mod linux { let _ = mapping.set_name(); - Ok(ProcessContextHandle { - mapping, - payload, - publisher_pid: getpid(), - }) + Ok(ProcessContextHandle { mapping, payload }) } /// Updates the context after initial publication. Currently unimplemented (always returns From bb80f432495e77196f17eb94cd4b521ecc3561b3 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 23 Feb 2026 16:25:31 +0100 Subject: [PATCH 11/23] chore: unify the signature of update and publish --- libdd-library-config/src/otel_process_ctxt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctxt.rs index 0aff947cc8..5ac086bc8a 100644 --- a/libdd-library-config/src/otel_process_ctxt.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -277,7 +277,7 @@ pub mod linux { /// Updates the context after initial publication. Currently unimplemented (always returns /// `Err`). - fn update(&mut self) -> anyhow::Result<()> { + fn update(&mut self, _payload: Vec) -> anyhow::Result<()> { Err(anyhow::anyhow!( "process context update isn't implemented yet" )) @@ -314,7 +314,7 @@ pub mod linux { let mut guard = lock_context_handle()?; match &mut *guard { - Some(handler) => handler.update(), + Some(handler) => handler.update(payload), None => { *guard = Some(ProcessContextHandle::publish(payload)?); Ok(()) From f3b9cef8cd8830ee06b5be6d908b67540c57e738 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:17:36 +0100 Subject: [PATCH 12/23] fix: fix typo in otel_process_ctxt comment Co-authored-by: Ivo Anjo --- libdd-library-config/src/otel_process_ctxt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctxt.rs index 5ac086bc8a..cb1c848238 100644 --- a/libdd-library-config/src/otel_process_ctxt.rs +++ b/libdd-library-config/src/otel_process_ctxt.rs @@ -6,7 +6,7 @@ //! # A note on race conditions //! //! Process context sharing implies concurrently writing to a memory area that another process -//! might be actively reading. However, reading isn't done as direct memory accesses but go through +//! might be actively reading. However, reading isn't done as direct memory accesses but goes through //! the OS, so the Rust definition of race conditions doesn't really apply. /// Current version of the process context format From c42b97156e8a43f1919b9c9b433303dd009bbd3c Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:20:37 +0100 Subject: [PATCH 13/23] chore: otel_process_ctxt -> otel_process_ctx --- libdd-library-config/src/lib.rs | 2 +- .../src/{otel_process_ctxt.rs => otel_process_ctx.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename libdd-library-config/src/{otel_process_ctxt.rs => otel_process_ctx.rs} (100%) diff --git a/libdd-library-config/src/lib.rs b/libdd-library-config/src/lib.rs index ce07caea62..f243678c09 100644 --- a/libdd-library-config/src/lib.rs +++ b/libdd-library-config/src/lib.rs @@ -1,6 +1,6 @@ // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -pub mod otel_process_ctxt; +pub mod otel_process_ctx; pub mod tracer_metadata; use std::borrow::Cow; diff --git a/libdd-library-config/src/otel_process_ctxt.rs b/libdd-library-config/src/otel_process_ctx.rs similarity index 100% rename from libdd-library-config/src/otel_process_ctxt.rs rename to libdd-library-config/src/otel_process_ctx.rs From 555434de9fa3dd1dbf267d838f152a4cd670d195 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:22:39 +0100 Subject: [PATCH 14/23] chore: move consts into linux-specific module --- libdd-library-config/src/otel_process_ctx.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index cb1c848238..78ccc012b1 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -6,21 +6,12 @@ //! # A note on race conditions //! //! Process context sharing implies concurrently writing to a memory area that another process -//! might be actively reading. However, reading isn't done as direct memory accesses but goes through -//! the OS, so the Rust definition of race conditions doesn't really apply. - -/// Current version of the process context format -pub const PROCESS_CTX_VERSION: u32 = 2; -/// Signature bytes for identifying process context mappings -pub const SIGNATURE: &[u8; 8] = b"OTEL_CTX"; -/// The discoverable name of the memory mapping. -pub const MAPPING_NAME: &str = "OTEL_CTX"; +//! might be actively reading. However, reading isn't done as direct memory accesses but goes +//! through the OS, so the Rust definition of race conditions doesn't really apply. #[cfg(target_os = "linux")] #[cfg(target_has_atomic = "64")] pub mod linux { - use super::{MAPPING_NAME, PROCESS_CTX_VERSION, SIGNATURE}; - use std::{ ffi::c_void, mem::ManuallyDrop, @@ -42,6 +33,13 @@ pub mod linux { process::set_virtual_memory_region_name, }; + /// Current version of the process context format + pub const PROCESS_CTX_VERSION: u32 = 2; + /// Signature bytes for identifying process context mappings + pub const SIGNATURE: &[u8; 8] = b"OTEL_CTX"; + /// The discoverable name of the memory mapping. + pub const MAPPING_NAME: &str = "OTEL_CTX"; + /// The header structure written at the start of the mapping. This must match the C /// layout of the specification. /// From 2412e1ee31b54bc43dd607839289fef95f984b95 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:25:24 +0100 Subject: [PATCH 15/23] chore: fix incorrect code comment --- libdd-library-config/src/otel_process_ctx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index 78ccc012b1..f7d93f6f2a 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -95,8 +95,8 @@ pub mod linux { impl MemMapping { /// Creates a suitable memory mapping for the context protocol to be published. /// - /// `memfd` is the preferred method, but this function fallbacks to an anonymous mapping on - /// old kernels that don't support `memfd` (or if `memfd` failed). + /// `memfd` is the preferred method, but this function fallbacks to an anonymous mapping if + /// `memfd` failed for any reason. fn new() -> anyhow::Result { let size = mapping_size(); From 4045856fe33ae3b8cfc3ff16c64b59f6d6089db8 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:30:56 +0100 Subject: [PATCH 16/23] chore: extra doc about naming in otel process ctx --- libdd-library-config/src/otel_process_ctx.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index f7d93f6f2a..94b992bc5f 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -144,6 +144,11 @@ pub mod linux { } /// Makes this mapping discoverable by giving it a name. + /// + /// Note that naming must be unconditionally attempted, even on kernels where we might know + /// it will fail. It is ok for naming to fail - we must only make sure that at least we + /// tried, as per the + /// [spec](https://github.com/open-telemetry/opentelemetry-specification/pull/4719). fn set_name(&mut self) -> anyhow::Result<()> { // Safety: the invariants of `MemMapping` ensures that `start` is non null and comes // from a previous call to `mmap` of size `mapping_size()` From 11ab3fc0347690bd581905cdedcfac8fa977ab2b Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:33:48 +0100 Subject: [PATCH 17/23] chore: fix outdated comment --- libdd-library-config/src/otel_process_ctx.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index 94b992bc5f..03c001b420 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -148,7 +148,7 @@ pub mod linux { /// Note that naming must be unconditionally attempted, even on kernels where we might know /// it will fail. It is ok for naming to fail - we must only make sure that at least we /// tried, as per the - /// [spec](https://github.com/open-telemetry/opentelemetry-specification/pull/4719). + /// [spec](https://github.com/open-telemetry/opentelemetry-specification/pull/4719). fn set_name(&mut self) -> anyhow::Result<()> { // Safety: the invariants of `MemMapping` ensures that `start` is non null and comes // from a previous call to `mmap` of size `mapping_size()` @@ -163,8 +163,8 @@ pub mod linux { Ok(()) } - /// Unmaps the underlying memory region and close the memfd file descriptor, if set. This - /// has same effect as dropping `self`, but propagates potential errors. + /// Unmaps the underlying memory region. This has same effect as dropping `self`, but + /// propagates potential errors. fn free(mut self) -> anyhow::Result<()> { // Safety: We put `self` in a `ManuallyDrop`, which prevents drop and future calls to // `free()`. @@ -185,7 +185,8 @@ pub mod linux { /// This method must only be called once. After calling `unmap()`, no other method of /// `MemMapping` must be ever called on `self` again, including `unmap()` and `drop()`. /// - /// Practically, `self` must be put in a `ManuallyDrop` wrapper and forgotten. + /// Practically, `self` must be put in a `ManuallyDrop` wrapper and forgotten, or being in + /// the process of being dropped. unsafe fn unmap(&mut self) -> anyhow::Result<()> { unsafe { munmap(self.start_addr, mapping_size()).map_err(|errno| { From 5605fdd6dc51153d37181a075f0e46ebe1f7779a Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:37:59 +0100 Subject: [PATCH 18/23] chore: updated outdated safety comment --- libdd-library-config/src/otel_process_ctx.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index 03c001b420..03dc45e520 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -246,8 +246,8 @@ pub mod linux { unsafe { // Safety: MappingHeader is packed, thus have no alignment requirement. It points - // to a freshly mmaped region which is valid for writing at least PAGE_SIZE bytes, - // which is greater than the size of MappingHeader. + // to a freshly mmaped region which is valid for writing at least `mapping_size()`, + // which we make sure is greater than the size of MappingHeader. ptr::write( header, MappingHeader { @@ -288,8 +288,11 @@ pub mod linux { } } - // Rustix's page_size caches the value in a static atomic, so it's ok to call mapping_size() - // repeatedly; it won't result in a syscall each time. + // Whether this size depends on the page size or not in the future, Rustix's `page_size()` + // caches the value in a static atomic, so it's ok to call `mapping_size()` repeatedly; it + // won't result in a syscall each time. + // + // The returned size is guaranteed to be larger or equal to the size of `MappingHeader`. fn mapping_size() -> usize { page_size() } From 7427943728d6b3eb17576045f842505eddd7190b Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:39:49 +0100 Subject: [PATCH 19/23] chore: drop outdated mention in comment --- libdd-library-config/src/otel_process_ctx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index 03dc45e520..a8111fc5e7 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -329,8 +329,8 @@ pub mod linux { } } - /// Unmaps the region used to share the process context and close the associated file - /// descriptor, if any. If no context has ever been published, this is no-op. + /// Unmaps the region used to share the process context. If no context has ever been published, + /// this is no-op. /// /// A call to [publish] following an [unpublish] will create a new mapping. pub fn unpublish() -> anyhow::Result<()> { From 7b0aef28f403c2420fda1f17c031c6b3cf73211d Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:40:50 +0100 Subject: [PATCH 20/23] chore: remove superfluous comment --- libdd-library-config/src/otel_process_ctx.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index a8111fc5e7..bb0b7da4e4 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -359,9 +359,6 @@ pub mod linux { } /// Checks if a mapping line refers to the OTEL_CTX mapping. - /// - /// Handles both anonymous naming (`[anon:OTEL_CTX]`) and memfd naming - /// (`/memfd:OTEL_CTX` which may have ` (deleted)` suffix). fn is_named_otel_mapping(line: &str) -> bool { let trimmed = line.trim_end(); From faff61cbc97cc8ee707d7b7eba161cc4f52fdddc Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:41:47 +0100 Subject: [PATCH 21/23] chore: fix typo in comment Co-authored-by: Ivo Anjo --- libdd-library-config/src/otel_process_ctx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index bb0b7da4e4..a7ac997988 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -432,7 +432,7 @@ pub mod linux { super::publish(payload.as_bytes().to_vec()) .expect("couldn't publish the process context"); - let header = read_process_context().expect("couldn't read back the process contex"); + let header = read_process_context().expect("couldn't read back the process context"); // Safety: the published context must have put valid bytes of size payload_size in the // context if the signature check succeded. let read_payload = unsafe { From f184f02e8a0429bab6bb1d4bceac6a776647a931 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:56:05 +0100 Subject: [PATCH 22/23] fix: proper ordering for context writer --- libdd-library-config/src/otel_process_ctx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index a7ac997988..9469aaa0a5 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -268,10 +268,10 @@ pub mod linux { // // To do so, we implement synchronization during publication _as if the reader were // another thread of this program_, using atomics and fences. + fence(Ordering::SeqCst); AtomicU64::from_ptr((*header).signature.as_mut_ptr().cast::()) // To avoid shuffling bytes, we must use the native endianness - .store(u64::from_ne_bytes(*SIGNATURE), Ordering::Release); - fence(Ordering::SeqCst); + .store(u64::from_ne_bytes(*SIGNATURE), Ordering::Relaxed); } let _ = mapping.set_name(); From 0af80fa7b3acb821f71d28a6bb0a324bb4b76444 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 Mar 2026 13:56:36 +0100 Subject: [PATCH 23/23] feat: shrink mapping size to the header size --- libdd-library-config/src/otel_process_ctx.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libdd-library-config/src/otel_process_ctx.rs b/libdd-library-config/src/otel_process_ctx.rs index 9469aaa0a5..9a7489c4e1 100644 --- a/libdd-library-config/src/otel_process_ctx.rs +++ b/libdd-library-config/src/otel_process_ctx.rs @@ -29,7 +29,6 @@ pub mod linux { use rustix::{ fs::{ftruncate, memfd_create, MemfdFlags}, mm::{madvise, mmap, mmap_anonymous, munmap, Advice, MapFlags, ProtFlags}, - param::page_size, process::set_virtual_memory_region_name, }; @@ -294,7 +293,7 @@ pub mod linux { // // The returned size is guaranteed to be larger or equal to the size of `MappingHeader`. fn mapping_size() -> usize { - page_size() + size_of::() } fn time_now_ns() -> Option {