@@ -22,7 +22,7 @@ use std::ffi::{c_char, OsStr};
2222use std:: path:: Path ;
2323use std:: ptr:: null_mut;
2424use uuid:: Uuid ;
25-
25+ use datadog_sidecar :: crashtracker :: crashtracker_receiver_request_bytes ;
2626pub use libdd_crashtracker_ffi:: * ;
2727pub use libdd_library_config_ffi:: * ;
2828pub use datadog_sidecar_ffi:: * ;
@@ -196,26 +196,144 @@ pub unsafe extern "C" fn datadog_otel_metrics_endpoint_from_agent_url(url: CharS
196196 }
197197}
198198
199- #[ no_mangle]
199+ /// Initialize crashtracking, selecting the receiver strategy for this process:
200+ /// - Linux, sidecar host (`master_pid == getpid()`): the in-process thread-mode sidecar can't
201+ /// serve its own crash, so spawn a fork+exec subprocess receiver (like the standalone daemon),
202+ /// resolving frames there since a crashing process can't reliably symbolize itself.
203+ /// - Linux, worker/collector: connect to the sidecar IPC socket and upgrade it to a crashtracker
204+ /// receiver on crash (`SOCK_SEQPACKET` + `enter_crashtracker_receiver`), streaming the report
205+ /// over that single socket and resolving frames in-process.
206+ /// - other unix (macOS): no sidecar upgrade; the default connector reaches the socket path.
207+ ///
208+ /// `master_pid` is the thread-mode master listener PID (0 if none): it keys the IPC socket and, on
209+ /// Linux, distinguishes the host from a worker.
210+ ///
211+ /// # Safety
212+ /// `endpoint` must point to a valid `Endpoint`; `metadata`'s borrowed strings/tags must outlive the
213+ /// call (they are copied into owned storage before it returns).
200214#[ cfg( unix) ]
201- pub unsafe extern "C" fn datadog_endpoint_as_crashtracker_config (
215+ #[ no_mangle]
216+ #[ allow( clippy:: missing_safety_doc) ]
217+ pub unsafe extern "C" fn datadog_crashtracker_init (
202218 endpoint : & Endpoint ,
203- callback : unsafe extern "C" fn ( EndpointConfig < ' _ > , * mut std:: ffi:: c_void ) ,
204- userdata : * mut std:: ffi:: c_void ,
205- ) {
206- let url_str = endpoint. url . to_string ( ) ;
207- unsafe {
208- callback (
209- EndpointConfig {
210- url : CharSlice :: from ( url_str. as_str ( ) ) ,
211- api_key : CharSlice :: from ( endpoint. api_key . as_deref ( ) . unwrap_or ( "" ) ) ,
212- test_token : CharSlice :: from ( endpoint. test_token . as_deref ( ) . unwrap_or ( "" ) ) ,
213- timeout : endpoint. timeout_ms ,
214- use_system_resolver : endpoint. use_system_resolver ,
215- } ,
216- userdata,
217- ) ;
219+ metadata : Metadata ,
220+ master_pid : i32 ,
221+ ) -> MaybeError {
222+ use libdd_crashtracker:: { CrashtrackerConfiguration , StacktraceCollection } ;
223+
224+ let result = ( || -> anyhow:: Result < ( ) > {
225+ let metadata: libdd_crashtracker:: Metadata = metadata. try_into ( ) ?;
226+
227+ let mut builder = CrashtrackerConfiguration :: builder ( )
228+ . collect_all_threads ( true )
229+ . timeout ( std:: time:: Duration :: from_millis ( 5000 ) )
230+ . endpoint_use_system_resolver ( endpoint. use_system_resolver )
231+ . endpoint_url ( & endpoint. url . to_string ( ) ) ;
232+ if let Some ( api_key) = endpoint. api_key . as_deref ( ) {
233+ builder = builder. endpoint_api_key ( api_key) ;
234+ }
235+ if let Some ( test_token) = endpoint. test_token . as_deref ( ) {
236+ builder = builder. endpoint_test_token ( test_token) ;
237+ }
238+ if endpoint. timeout_ms != 0 {
239+ builder = builder. endpoint_timeout_ms ( endpoint. timeout_ms ) ;
240+ }
241+
242+ #[ cfg( target_os = "linux" ) ]
243+ {
244+ // Worker/collector: open a fresh connection to the sidecar IPC socket, upgrade it with
245+ // the SEQPACKET connector, and resolve frames in-process.
246+ if master_pid == 0 || master_pid != std:: process:: id ( ) as i32 {
247+ let socket_path = datadog_sidecar:: crashtracker:: crashtracker_ipc_socket_path (
248+ master_pid as u32 ,
249+ datadog_sidecar:: config:: FromEnv :: ipc_mode ( ) ,
250+ ) ;
251+ // Prime the request bytes outside the crash handler so the connector never
252+ // allocates in signal context.
253+ let _ = crashtracker_receiver_request_bytes ( ) ;
254+ let config = builder
255+ . resolve_frames ( StacktraceCollection :: EnabledWithInprocessSymbols )
256+ . unix_socket_path ( socket_path. to_string_lossy ( ) . into_owned ( ) )
257+ . unix_socket_connector (
258+ datadog_sidecar:: crashtracker:: connect_to_sidecar_receiver,
259+ )
260+ . build ( ) ?;
261+ return libdd_crashtracker:: init (
262+ config,
263+ libdd_crashtracker:: CrashtrackerReceiverConfig :: default ( ) ,
264+ metadata,
265+ ) ;
266+ }
267+ // Thread-mode host: its in-process sidecar can't serve its own crash, so spawn a
268+ // transient fork+exec subprocess receiver and resolve frames there.
269+ let config = builder
270+ . resolve_frames ( StacktraceCollection :: EnabledWithSymbolsInReceiver )
271+ . build ( ) ?;
272+ let receiver_config =
273+ datadog_sidecar:: build_crashtracker_receiver_config ( None , None ) ?;
274+ libdd_crashtracker:: init ( config, receiver_config, metadata)
275+ }
276+
277+ // macOS can't open a fresh SOCK_SEQPACKET connection signal-safely, so reuse the
278+ // already-open sidecar fd and upgrade it at crash time (no-op if there's no connection).
279+ // The path is a placeholder the connector ignores; it just has to be non-empty so the
280+ // crashtracker takes the connector path.
281+ #[ cfg( target_os = "macos" ) ]
282+ {
283+ // Prime the request bytes outside the crash handler so the connector never
284+ // allocates in signal context.
285+ let _ = crashtracker_receiver_request_bytes ( ) ;
286+ let config = builder
287+ . resolve_frames ( StacktraceCollection :: EnabledWithInprocessSymbols )
288+ . unix_socket_path ( "datadog-sidecar-crashtracker" . to_string ( ) )
289+ . unix_socket_connector ( reuse_sidecar_fd_connector)
290+ . build ( ) ?;
291+ libdd_crashtracker:: init (
292+ config,
293+ libdd_crashtracker:: CrashtrackerReceiverConfig :: default ( ) ,
294+ metadata,
295+ )
296+ }
297+
298+ #[ cfg( not( any( target_os = "linux" , target_os = "macos" ) ) ) ]
299+ {
300+ let _ = ( master_pid, builder, metadata) ;
301+ Ok ( ( ) )
302+ }
303+ } ) ( ) ;
304+ match result {
305+ Ok ( ( ) ) => MaybeError :: None ,
306+ Err ( e) => {
307+ MaybeError :: Some ( Error :: from ( format ! ( "{e:?}" ) ) )
308+ }
309+ }
310+ }
311+
312+ /// On macos we cannot easily create a new signal safe connection to the sidecar, so we reuse the
313+ /// already open fd from datadog_sidecar_for_signal.
314+ #[ cfg( target_os = "macos" ) ]
315+ fn reuse_sidecar_fd_connector ( _unix_socket_path : & str ) -> std:: os:: fd:: RawFd {
316+ extern "C" {
317+ // Set by the sidecar connect path (sidecar.c) to the live transport for best-effort
318+ // signal-handler use; null when there is no connection. The transport pointer is stable
319+ // across transparent reconnects (only its inner sender is swapped), so reading the fd
320+ // through it stays current. Typed as an opaque pointer to keep the `extern` block FFI-safe;
321+ // cast to the real type below.
322+ static mut datadog_sidecar_for_signal: * mut std:: ffi:: c_void ;
323+ }
324+
325+ // Best-effort, signal context: read the transport pointer and get its current fd via
326+ // SidecarTransport::signal_fd (which uses get_mut, never locking). Going through the raw
327+ // pointer knowingly bypasses aliasing checks — the crashing thread is the only realistic
328+ // accessor.
329+ let transport = unsafe { datadog_sidecar_for_signal }
330+ as * mut datadog_sidecar:: service:: blocking:: SidecarTransport ;
331+ if transport. is_null ( ) {
332+ return -1 ;
218333 }
334+ let fd = unsafe { ( * transport) . as_raw_fd ( ) } ;
335+ let bytes = crashtracker_receiver_request_bytes ( ) ;
336+ let sent = unsafe { libc:: send ( dup, bytes. as_ptr ( ) as * const libc:: c_void , bytes. len ( ) , 0 ) } ;
219337}
220338
221339// Hack: Without this, the PECL build of the tracer does not contain the ddog_library_* functions
0 commit comments