Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 151 additions & 129 deletions components-rs/common.h

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions components-rs/crashtracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ void ddog_crasht_CrashInfoBuilder_drop(struct ddog_crasht_Handle_CrashInfoBuilde
* which has not previously been dropped.
*/
DDOG_CHECK_RETURN
struct ddog_crasht_CrashInfo_NewResult ddog_crasht_CrashInfoBuilder_build(struct ddog_crasht_Handle_CrashInfoBuilder *builder);
ddog_crasht_CrashInfo_NewResult ddog_crasht_CrashInfoBuilder_build(struct ddog_crasht_Handle_CrashInfoBuilder *builder);

/**
* # Safety
Expand Down Expand Up @@ -854,7 +854,7 @@ struct ddog_StringWrapperResult ddog_crasht_demangle(ddog_CharSlice name,
* signal handler is dangerous, so we fork a sidecar to do the stuff we aren't
* allowed to do in the handler.
*
* See comments in [datadog-crashtracker/lib.rs] for a full architecture description.
* See comments in [libdd-crashtracker/lib.rs] for a full architecture description.
* # Safety
* No safety concerns
*/
Expand All @@ -868,7 +868,7 @@ DDOG_CHECK_RETURN struct ddog_VoidResult ddog_crasht_receiver_entry_point_stdin(
* signal handler is dangerous, so we fork a sidecar to do the stuff we aren't
* allowed to do in the handler.
*
* See comments in [datadog-crashtracker/lib.rs] for a full architecture
* See comments in [libdd-crashtracker/lib.rs] for a full architecture
* description.
* # Safety
* No safety concerns
Expand Down
69 changes: 68 additions & 1 deletion components-rs/sidecar.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,72 @@ void ddog_sidecar_transport_drop(struct ddog_SidecarTransport*);
*/
ddog_MaybeError ddog_sidecar_connect(struct ddog_SidecarTransport **connection);

/**
* Start master listener thread for thread-based connections (Unix only).
*
* This spawns a listener thread that accepts worker connections. Only one
* master listener can be active per process.
*
* # Arguments
* * `pid` - Process ID that owns this listener
*
* # Returns
* * `MaybeError::None` on success
* * `MaybeError::Some(Error)` on failure
*/
ddog_MaybeError ddog_sidecar_connect_master(int32_t pid);

/**
* Connect as worker to master listener thread (Unix only).
*
* Establishes a connection to the master listener for the given PID.
*
* # Arguments
* * `pid` - Process ID of the master process
* * `connection` - Output parameter for the connection
*
* # Returns
* * `MaybeError::None` on success
* * `MaybeError::Some(Error)` on failure
*/
ddog_MaybeError ddog_sidecar_connect_worker(int32_t pid, struct ddog_SidecarTransport **connection);

/**
* Shutdown the master listener thread (Unix only).
*
* Sends shutdown signal and joins the listener thread. This is blocking.
*
* # Returns
* * `MaybeError::None` on success
* * `MaybeError::Some(Error)` on failure
*/
ddog_MaybeError ddog_sidecar_shutdown_master_listener(void);

/**
* Check if master listener is active for the given PID (Unix only).
*
* Used for fork detection.
*
* # Arguments
* * `pid` - Process ID to check
*
* # Returns
* * `true` if listener is active for this PID
* * `false` otherwise
*/
bool ddog_sidecar_is_master_listener_active(int32_t pid);

/**
* Clear inherited master listener state in child after fork (Unix only).
*
* Child processes must call this to avoid using the parent's listener.
*
* # Returns
* * `MaybeError::None` on success
* * `MaybeError::Some(Error)` on failure
*/
ddog_MaybeError ddog_sidecar_clear_inherited_listener(void);

ddog_MaybeError ddog_sidecar_ping(struct ddog_SidecarTransport **transport);

ddog_MaybeError ddog_sidecar_flush_traces(struct ddog_SidecarTransport **transport);
Expand All @@ -118,7 +184,8 @@ ddog_MaybeError ddog_sidecar_telemetry_enqueueConfig(struct ddog_SidecarTranspor
ddog_CharSlice config_key,
ddog_CharSlice config_value,
enum ddog_ConfigurationOrigin origin,
ddog_CharSlice config_id);
ddog_CharSlice config_id,
struct ddog_Option_U64 seq_id);

/**
* Reports a dependency to the telemetry.
Expand Down
3 changes: 2 additions & 1 deletion components-rs/telemetry.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ ddog_MaybeError ddog_telemetry_builder_with_config(struct ddog_TelemetryWorkerBu
ddog_CharSlice name,
ddog_CharSlice value,
enum ddog_ConfigurationOrigin origin,
ddog_CharSlice config_id);
ddog_CharSlice config_id,
struct ddog_Option_U64 seq_id);

/**
* Builds the telemetry worker and return a handle to it
Expand Down
9 changes: 9 additions & 0 deletions ext/coms.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,10 +799,13 @@ static void dd_agent_headers_free(struct curl_slist *list) {

void ddtrace_coms_curl_shutdown(void) {
dd_agent_headers_free(dd_agent_curl_headers);
dd_agent_curl_headers = NULL; // Prevent double-free

if (dd_agent_config_writer) {
ddog_agent_remote_config_writer_drop(dd_agent_config_writer);
ddog_drop_anon_shm_handle(ddtrace_coms_agent_config_handle);
dd_agent_config_writer = NULL; // Prevent double-free
ddtrace_coms_agent_config_handle = NULL; // Prevent double-free
}
}

Expand Down Expand Up @@ -1500,6 +1503,12 @@ bool ddtrace_coms_flush_shutdown_writer_synchronous(void) {

bool ddtrace_coms_synchronous_flush(uint32_t timeout) {
struct _writer_loop_data_t *writer = _dd_get_writer();

// Check if writer thread exists before attempting to use it
if (!writer->thread) {
return false;
}

uint32_t previous_writer_cycle = atomic_load(&writer->writer_cycle);
uint32_t previous_processed_stacks_total = atomic_load(&writer->flush_processed_stacks_total);
int64_t old_flush_interval = atomic_load(&writer->flush_interval);
Expand Down
15 changes: 15 additions & 0 deletions ext/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ static bool dd_parse_sampling_rules_format(zai_str value, zval *decoded_value, b
return true;
}

static bool dd_parse_sidecar_connection_mode(zai_str value, zval *decoded_value, bool persistent) {
UNUSED(persistent);
if (zai_str_eq_ci_cstr(value, "auto")) {
ZVAL_LONG(decoded_value, DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO);
} else if (zai_str_eq_ci_cstr(value, "subprocess")) {
ZVAL_LONG(decoded_value, DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS);
} else if (zai_str_eq_ci_cstr(value, "thread")) {
ZVAL_LONG(decoded_value, DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD);
} else {
return false;
}

return true;
}

static bool dd_parse_tags(zai_str value, zval *decoded_value, bool persistent) {
ZVAL_ARR(decoded_value, pemalloc(sizeof(HashTable), persistent));
zend_hash_init(Z_ARR_P(decoded_value), 8, NULL, persistent ? ZVAL_INTERNAL_PTR_DTOR : ZVAL_PTR_DTOR, persistent);
Expand Down
7 changes: 7 additions & 0 deletions ext/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ enum ddtrace_sampling_rules_format {
DD_TRACE_SAMPLING_RULES_FORMAT_GLOB
};

enum ddtrace_sidecar_connection_mode {
DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO = 0, // Default: try subprocess, fallback to thread
DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS = 1, // Force subprocess only
DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD = 2, // Force thread only
};

/* From the curl docs on CONNECT_TIMEOUT_MS:
* If libcurl is built to use the standard system name resolver, that
* portion of the transfer will still use full-second resolution for
Expand Down Expand Up @@ -225,6 +231,7 @@ enum ddtrace_sampling_rules_format {
CONFIG(STRING, DD_TRACE_AGENT_TEST_SESSION_TOKEN, "", .ini_change = ddtrace_alter_test_session_token) \
CONFIG(BOOL, DD_TRACE_PROPAGATE_USER_ID_DEFAULT, "false") \
CONFIG(CUSTOM(INT), DD_DBM_PROPAGATION_MODE, "disabled", .parser = dd_parse_dbm_mode) \
CONFIG(CUSTOM(INT), DD_TRACE_SIDECAR_CONNECTION_MODE, "auto", .parser = dd_parse_sidecar_connection_mode) \
CONFIG(SET, DD_TRACE_WORDPRESS_ADDITIONAL_ACTIONS, "") \
CONFIG(BOOL, DD_TRACE_WORDPRESS_CALLBACKS, "true") \
CONFIG(BOOL, DD_INTEGRATION_METRICS_ENABLED, "true", \
Expand Down
26 changes: 24 additions & 2 deletions ext/ddtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,7 @@ static PHP_MINIT_FUNCTION(ddtrace) {
#endif
ddshared_minit();
ddtrace_autoload_minit();
ddtrace_sidecar_minit();

dd_register_span_data_ce();
dd_register_fatal_error_ce();
Expand Down Expand Up @@ -1585,7 +1586,9 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) {
#ifndef _WIN32
ddtrace_signals_mshutdown();

if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) {
// For CLI SAPI, background sender is already shut down in RSHUTDOWN
// For non-CLI SAPIs, shut it down here in MSHUTDOWN
if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER() && strcmp(sapi_module.name, "cli") != 0) {
ddtrace_coms_mshutdown();
if (ddtrace_coms_flush_shutdown_writer_synchronous()) {
ddtrace_coms_curl_shutdown();
Expand All @@ -1612,7 +1615,11 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) {

ddtrace_user_req_shutdown();

ddtrace_sidecar_shutdown();
// Only shutdown sidecar in MSHUTDOWN for non-CLI SAPIs
// CLI SAPI shuts down in RSHUTDOWN to allow thread joins before ASAN checks
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does ASAN influence this? ASAN is supposed to run after MSHUTDOWN as well?
I suspect that is related to the timings and the fact that the libdatadog part doesn't properly handle shutdown currently.
And this should in fact always shutdown the sidecar in MSHUTDOWN.

if (strcmp(sapi_module.name, "cli") != 0) {
ddtrace_sidecar_shutdown();
}

ddtrace_live_debugger_mshutdown();

Expand Down Expand Up @@ -2632,6 +2639,21 @@ void dd_internal_handle_fork(void) {
ddtrace_coms_curl_shutdown();
ddtrace_coms_clean_background_sender_after_fork();
}

// Handle thread mode after fork
int32_t current_pid = (int32_t)getpid();
bool is_child_process = (ddtrace_sidecar_master_pid != 0 &&
current_pid != ddtrace_sidecar_master_pid);

if (is_child_process && ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) {
// Clear inherited master listener state (child doesn't own it)
ddtrace_ffi_try("Failed clearing inherited listener state",
ddog_sidecar_clear_inherited_listener());

// Don't try to reconnect in thread mode after fork
// Let sidecar stay unavailable
LOG(WARN, "Child process after fork with thread mode: sidecar unavailable");
Comment on lines +2653 to +2655
Copy link
Collaborator

@bwoebi bwoebi Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm, no? Just connect to the parents process socket? At least attempt to.

}
#endif
if (DDTRACE_G(agent_config_reader)) {
ddog_agent_remote_config_reader_drop(DDTRACE_G(agent_config_reader));
Expand Down
3 changes: 3 additions & 0 deletions ext/ddtrace_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ static void register_ddtrace_symbols(int module_number)
REGISTER_LONG_CONSTANT("DDTrace\\DBM_PROPAGATION_DISABLED", DD_TRACE_DBM_PROPAGATION_DISABLED, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\DBM_PROPAGATION_SERVICE", DD_TRACE_DBM_PROPAGATION_SERVICE, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\DBM_PROPAGATION_FULL", DD_TRACE_DBM_PROPAGATION_FULL, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\SIDECAR_CONNECTION_MODE_AUTO", DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\SIDECAR_CONNECTION_MODE_SUBPROCESS", DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\SIDECAR_CONNECTION_MODE_THREAD", DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\Internal\\SPAN_FLAG_OPENTELEMETRY", DDTRACE_SPAN_FLAG_OPENTELEMETRY, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("DDTrace\\Internal\\SPAN_FLAG_OPENTRACING", DDTRACE_SPAN_FLAG_OPENTRACING, CONST_PERSISTENT);
REGISTER_STRING_CONSTANT("DD_TRACE_VERSION", PHP_DDTRACE_VERSION, CONST_PERSISTENT);
Expand Down
9 changes: 9 additions & 0 deletions ext/handlers_pcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ static void dd_prefork() {

static void dd_handle_fork(zval *return_value) {
if (Z_LVAL_P(return_value) == 0) {
// CHILD PROCESS

// Warn if thread mode is active
if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) {
LOG(WARN, "pcntl_fork() detected with thread-based sidecar connection. "
"Thread mode is incompatible with fork and may cause instability. "
"Consider using subprocess mode (DD_TRACE_SIDECAR_CONNECTION_MODE=subprocess).");
Comment on lines +44 to +46
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is? Why?

}

dd_internal_handle_fork();
} else {
#if JOIN_BGS_BEFORE_FORK
Expand Down
Loading
Loading