@@ -27,18 +27,92 @@ static zif_handler dd_pcntl_forkx_handler = NULL;
2727#define JOIN_BGS_BEFORE_FORK 1
2828#endif
2929
30+ static bool dd_master_listener_was_active = false;
31+
3032static void dd_prefork () {
3133#if JOIN_BGS_BEFORE_FORK
3234 if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER ()) {
3335 ddtrace_coms_flush_shutdown_writer_synchronous ();
3436 }
3537#endif
38+
39+ #ifndef _WIN32
40+ // Check if master listener is active before fork
41+ dd_master_listener_was_active = (ddtrace_master_pid != 0 && getpid () == ddtrace_master_pid );
42+
43+ if (dd_master_listener_was_active ) {
44+ // Shutdown master listener before fork to avoid Tokio runtime corruption.
45+ // Tokio's async runtime and fork() are fundamentally incompatible - forking
46+ // while Tokio threads are active causes state corruption in the parent process.
47+ // See: https://github.com/tokio-rs/tokio/issues/4301
48+
49+ // First close parent's worker connection to prevent handler thread deadlock
50+ if (ddtrace_sidecar ) {
51+ ddog_sidecar_transport_drop (ddtrace_sidecar );
52+ ddtrace_sidecar = NULL ;
53+ }
54+
55+ // Then shutdown the master listener and its Tokio runtime
56+ ddog_sidecar_shutdown_master_listener ();
57+ }
58+ #endif
3659}
3760
61+ static void dd_postfork_parent () {
62+ #ifndef _WIN32
63+ // Restart master listener in parent if it was active before fork
64+ if (dd_master_listener_was_active ) {
65+ // Reinitialize master listener with fresh Tokio runtime.
66+ // This recreates the async runtime that was shut down before fork.
67+
68+ // First, restart the master listener thread
69+ if (!ddtrace_ffi_try ("Failed restarting sidecar master listener after fork" ,
70+ ddog_sidecar_connect_master ((int32_t )ddtrace_master_pid ))) {
71+ LOG (WARN , "Failed to restart sidecar master listener after fork" );
72+ dd_master_listener_was_active = false;
73+ return ;
74+ }
75+
76+ // Then reconnect to it as a worker
77+ ddog_SidecarTransport * sidecar_transport = NULL ;
78+ if (!ddtrace_ffi_try ("Failed reconnecting master to sidecar after fork" ,
79+ ddog_sidecar_connect_worker ((int32_t )ddtrace_master_pid , & sidecar_transport ))) {
80+ LOG (WARN , "Failed to reconnect master process to sidecar after fork" );
81+ dd_master_listener_was_active = false;
82+ return ;
83+ }
84+
85+ ddtrace_sidecar = sidecar_transport ;
86+ dd_master_listener_was_active = false;
87+ }
88+ #endif
89+ }
90+
91+ // Declare LSAN runtime interface functions
92+ #if defined(__SANITIZE_ADDRESS__ ) && !defined(_WIN32 )
93+ void __lsan_disable (void );
94+ void __lsan_enable (void );
95+ #endif
96+
3897static void dd_handle_fork (zval * return_value ) {
3998 if (Z_LVAL_P (return_value ) == 0 ) {
99+ // CHILD PROCESS
100+ // Disable ASAN leak detection in child to avoid false positives from inherited
101+ // Tokio TLS and runtime state that can't be properly cleaned up after fork
102+ #if defined(__SANITIZE_ADDRESS__ ) && !defined(_WIN32 )
103+ // The child inherits Tokio runtime thread-local storage from the parent's
104+ // master listener and connection handlers. These TLS destructors reference
105+ // threads that don't exist in the child, causing spurious leak reports.
106+ // We disable leak detection in the child since the inherited memory will
107+ // be reclaimed when the child process exits.
108+ __lsan_disable ();
109+ #endif
40110 dd_internal_handle_fork ();
41111 } else {
112+ // PARENT PROCESS
113+ // Restart master listener if it was shut down before fork
114+ dd_postfork_parent ();
115+
42116#if JOIN_BGS_BEFORE_FORK
43117 if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER ()) {
44118 ddtrace_coms_restart_writer ();
0 commit comments