Skip to content

Commit 818c03c

Browse files
committed
Expose fork safety hooks through trace exporter FFI
Add `ddog_trace_exporter_before_fork`, `_after_fork_in_parent`, and `_after_fork_in_child` that delegate to the underlying SharedRuntime. These allow C callers to coordinate the tokio runtime lifecycle around process forks.
1 parent 1fb8aca commit 818c03c

2 files changed

Lines changed: 119 additions & 0 deletions

File tree

  • libdd-data-pipeline-ffi/src
  • libdd-data-pipeline/src/trace_exporter

libdd-data-pipeline-ffi/src/tracer.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,91 @@ pub unsafe extern "C" fn ddog_trace_exporter_send_trace_chunks(
342342
)
343343
}
344344

345+
// ---------------------------------------------------------------------------
346+
// Fork safety hooks
347+
// ---------------------------------------------------------------------------
348+
349+
/// Must be called in the parent process before `fork()`.
350+
///
351+
/// Pauses all workers on the exporter's [`SharedRuntime`] so that no
352+
/// background threads are running during the fork.
353+
///
354+
/// # Safety
355+
///
356+
/// * `exporter` must be a valid `TraceExporter` pointer or null.
357+
#[no_mangle]
358+
pub unsafe extern "C" fn ddog_trace_exporter_before_fork(
359+
exporter: Option<&TraceExporter>,
360+
) -> Option<Box<ExporterError>> {
361+
let Some(exporter) = exporter else {
362+
return gen_error!(ErrorCode::InvalidArgument);
363+
};
364+
365+
catch_panic!(
366+
{
367+
exporter.shared_runtime().before_fork();
368+
None
369+
},
370+
gen_error!(ErrorCode::Panic)
371+
)
372+
}
373+
374+
/// Must be called in the parent process after `fork()`.
375+
///
376+
/// Restarts workers that were paused by
377+
/// [`ddog_trace_exporter_before_fork`].
378+
///
379+
/// # Safety
380+
///
381+
/// * `exporter` must be a valid `TraceExporter` pointer or null.
382+
#[no_mangle]
383+
pub unsafe extern "C" fn ddog_trace_exporter_after_fork_in_parent(
384+
exporter: Option<&TraceExporter>,
385+
) -> Option<Box<ExporterError>> {
386+
let Some(exporter) = exporter else {
387+
return gen_error!(ErrorCode::InvalidArgument);
388+
};
389+
390+
catch_panic!(
391+
match exporter.shared_runtime().after_fork_parent() {
392+
Ok(()) => None,
393+
Err(e) => Some(Box::new(ExporterError::new(
394+
ErrorCode::Internal,
395+
&e.to_string(),
396+
))),
397+
},
398+
gen_error!(ErrorCode::Panic)
399+
)
400+
}
401+
402+
/// Must be called in the child process after `fork()`.
403+
///
404+
/// Creates a fresh tokio runtime and restarts all workers on the
405+
/// exporter's [`SharedRuntime`].
406+
///
407+
/// # Safety
408+
///
409+
/// * `exporter` must be a valid `TraceExporter` pointer or null.
410+
#[no_mangle]
411+
pub unsafe extern "C" fn ddog_trace_exporter_after_fork_in_child(
412+
exporter: Option<&TraceExporter>,
413+
) -> Option<Box<ExporterError>> {
414+
let Some(exporter) = exporter else {
415+
return gen_error!(ErrorCode::InvalidArgument);
416+
};
417+
418+
catch_panic!(
419+
match exporter.shared_runtime().after_fork_child() {
420+
Ok(()) => None,
421+
Err(e) => Some(Box::new(ExporterError::new(
422+
ErrorCode::Internal,
423+
&e.to_string(),
424+
))),
425+
},
426+
gen_error!(ErrorCode::Panic)
427+
)
428+
}
429+
345430
#[cfg(test)]
346431
mod tests {
347432
use super::*;
@@ -687,4 +772,33 @@ mod tests {
687772
ddog_tracer_trace_chunks_free(chunks);
688773
}
689774
}
775+
776+
// -- Fork safety hooks --------------------------------------------------
777+
778+
#[test]
779+
fn before_fork_null_returns_error() {
780+
unsafe {
781+
let err = ddog_trace_exporter_before_fork(None);
782+
assert!(err.is_some());
783+
ddog_trace_exporter_error_free(err);
784+
}
785+
}
786+
787+
#[test]
788+
fn after_fork_in_parent_null_returns_error() {
789+
unsafe {
790+
let err = ddog_trace_exporter_after_fork_in_parent(None);
791+
assert!(err.is_some());
792+
ddog_trace_exporter_error_free(err);
793+
}
794+
}
795+
796+
#[test]
797+
fn after_fork_in_child_null_returns_error() {
798+
unsafe {
799+
let err = ddog_trace_exporter_after_fork_in_child(None);
800+
assert!(err.is_some());
801+
ddog_trace_exporter_error_free(err);
802+
}
803+
}
690804
}

libdd-data-pipeline/src/trace_exporter/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ impl<C: HttpClientCapability + SleepCapability + MaybeSend + Sync + 'static> Tra
209209
TraceExporterBuilder::default()
210210
}
211211

212+
/// Returns a reference to the underlying [`SharedRuntime`].
213+
pub fn shared_runtime(&self) -> &SharedRuntime {
214+
&self.shared_runtime
215+
}
216+
212217
/// Stop the background workers owned by this exporter.
213218
///
214219
/// Only the workers spawned for this exporter are stopped. Workers from other components

0 commit comments

Comments
 (0)