@@ -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) ]
346431mod 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}
0 commit comments