From 63b535325a9c4e17a68d838a5153129db972a210 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 11:34:58 +0100 Subject: [PATCH 1/6] feat(tracer): add configuration for connection mode Signed-off-by: Alexandre Rulleau --- ext/configuration.c | 15 +++++++++++++++ ext/configuration.h | 7 +++++++ ext/ddtrace_arginfo.h | 3 +++ 3 files changed, 25 insertions(+) diff --git a/ext/configuration.c b/ext/configuration.c index 5e6e0bfca77..71fa4c0951a 100644 --- a/ext/configuration.c +++ b/ext/configuration.c @@ -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); diff --git a/ext/configuration.h b/ext/configuration.h index 362591df462..49e1a46f737 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -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 @@ -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", \ diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 9c005f69d09..a782ea94b39 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -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); From 4ee7f1cceeb76c0629fa69d38c7519a877c32c5c Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 11:35:17 +0100 Subject: [PATCH 2/6] feat(tracer): add sidecar thread listener module Signed-off-by: Alexandre Rulleau --- components-rs/ddtrace.h | 10 ++++++++++ components-rs/sidecar.h | 38 ++++++++++++++++++++++++++++++++++++++ libdatadog | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index 35d06a61820..6fe1ee39216 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -116,6 +116,16 @@ ddog_MaybeError ddog_sidecar_connect_php(struct ddog_SidecarTransport **connecti void ddtrace_sidecar_reconnect(struct ddog_SidecarTransport **transport, struct ddog_SidecarTransport *(*factory)(void)); +// Thread-based sidecar connection (Unix only) +#if !defined(_WIN32) +ddog_MaybeError ddog_sidecar_connect_master(int32_t pid); +ddog_MaybeError ddog_sidecar_connect_worker(int32_t pid, + struct ddog_SidecarTransport **connection); +ddog_MaybeError ddog_sidecar_shutdown_master_listener(void); +bool ddog_sidecar_is_master_listener_active(int32_t pid); +ddog_MaybeError ddog_sidecar_clear_inherited_listener(void); +#endif + bool ddog_shm_limiter_inc(const struct ddog_MaybeShmLimiter *limiter, uint32_t limit); bool ddog_exception_hash_limiter_inc(struct ddog_SidecarTransport *connection, diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 4746e0d2163..2701a73cd8e 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -92,6 +92,44 @@ 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. + */ +#if !defined(_WIN32) +ddog_MaybeError ddog_sidecar_connect_master(int32_t pid); +#endif + +/** + * Connect as worker to master listener thread (Unix only). + */ +#if !defined(_WIN32) +ddog_MaybeError ddog_sidecar_connect_worker(int32_t pid, + struct ddog_SidecarTransport **connection); +#endif + +/** + * Shutdown the master listener thread (Unix only). + */ +#if !defined(_WIN32) +ddog_MaybeError ddog_sidecar_shutdown_master_listener(void); +#endif + +/** + * Check if master listener is active for the given PID (Unix only). + */ +#if !defined(_WIN32) +bool ddog_sidecar_is_master_listener_active(int32_t pid); +#endif + +/** + * Clear inherited master listener state in child after fork (Unix only). + */ +#if !defined(_WIN32) +ddog_MaybeError ddog_sidecar_clear_inherited_listener(void); +#endif + ddog_MaybeError ddog_sidecar_ping(struct ddog_SidecarTransport **transport); ddog_MaybeError ddog_sidecar_flush_traces(struct ddog_SidecarTransport **transport); diff --git a/libdatadog b/libdatadog index 1caf15157a8..d87edf1022e 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 1caf15157a8251d398715349a75d5607cb5545c2 +Subproject commit d87edf1022e41e292841f886627319fd950a66fb From 38890f986bf97f3d8cda0767f27a7378c5179abd Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 12:10:00 +0100 Subject: [PATCH 3/6] feat(tracer): implement threaded connection fallback Signed-off-by: Alexandre Rulleau --- ext/ddtrace.c | 22 ++++- ext/handlers_pcntl.c | 9 ++ ext/sidecar.c | 216 ++++++++++++++++++++++++++++++++++++++++++- ext/sidecar.h | 16 ++++ 4 files changed, 260 insertions(+), 3 deletions(-) diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 251da2bc3a6..5b4b7ed62ac 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -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(); @@ -1612,7 +1613,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 + if (strcmp(sapi_module.name, "cli") != 0) { + ddtrace_sidecar_shutdown(); + } ddtrace_live_debugger_mshutdown(); @@ -2632,6 +2637,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"); + } #endif if (DDTRACE_G(agent_config_reader)) { ddog_agent_remote_config_reader_drop(DDTRACE_G(agent_config_reader)); diff --git a/ext/handlers_pcntl.c b/ext/handlers_pcntl.c index acff140cfe4..22b5b31d32a 100644 --- a/ext/handlers_pcntl.c +++ b/ext/handlers_pcntl.c @@ -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)."); + } + dd_internal_handle_fork(); } else { #if JOIN_BGS_BEFORE_FORK diff --git a/ext/sidecar.c b/ext/sidecar.c index 9732879acc5..cddd5645375 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -25,6 +25,10 @@ ddog_Endpoint *dogstatsd_endpoint; // always set when ddtrace_endpoint is set struct ddog_InstanceId *ddtrace_sidecar_instance_id; static uint8_t dd_sidecar_formatted_session_id[36]; +// Connection mode tracking +dd_sidecar_active_mode_t ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_NONE; +int32_t ddtrace_sidecar_master_pid = 0; + static inline void dd_set_endpoint_test_token(ddog_Endpoint *endpoint) { if (zai_config_is_initialized()) { if (ZSTR_LEN(get_DD_TRACE_AGENT_TEST_SESSION_TOKEN())) { @@ -158,6 +162,148 @@ static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { } +// Subprocess connection mode - current default behavior +ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void) { + if (!ddtrace_endpoint) { + return NULL; + } + ZEND_ASSERT(dogstatsd_endpoint != NULL); + + dd_set_endpoint_test_token(dogstatsd_endpoint); + +#ifdef _WIN32 + DDOG_PHP_FUNCTION = (const uint8_t *)zend_hash_func; +#endif + + char logpath[MAXPATHLEN]; + int error_fd = atomic_load(&ddtrace_error_log_fd); + if (error_fd == -1 || ddtrace_get_fd_path(error_fd, logpath) < 0) { + *logpath = 0; + } + + ddog_SidecarTransport *sidecar_transport; + if (!ddtrace_ffi_try("Failed connecting to sidecar (subprocess mode)", + ddog_sidecar_connect_php(&sidecar_transport, logpath, + dd_zend_string_to_CharSlice(get_global_DD_TRACE_LOG_LEVEL()), + get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), + dd_sidecar_on_reconnect, + ddtrace_endpoint))) { + return NULL; + } + + dd_sidecar_post_connect(&sidecar_transport, false, logpath); + + // Set active mode + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_SUBPROCESS; + + return sidecar_transport; +} + +// Thread connection mode - fallback when subprocess fails +ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { + if (!ddtrace_endpoint) { + return NULL; + } + ZEND_ASSERT(dogstatsd_endpoint != NULL); + +#ifndef _WIN32 + int32_t current_pid = (int32_t)getpid(); + bool is_master = (ddtrace_sidecar_master_pid == 0 || current_pid == ddtrace_sidecar_master_pid); + + if (is_master) { + // Set master PID + if (ddtrace_sidecar_master_pid == 0) { + ddtrace_sidecar_master_pid = current_pid; + } + + // Start master listener thread (only if not already running) + if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { + if (!ddtrace_ffi_try("Failed starting master listener thread", + ddog_sidecar_connect_master(ddtrace_sidecar_master_pid))) { + LOG(WARN, "Failed to start master listener thread"); + return NULL; + } + + LOG(INFO, "Started master listener thread (PID=%d)", ddtrace_sidecar_master_pid); + } + } + + // Connect as worker to master listener + ddog_SidecarTransport *sidecar_transport; + if (!ddtrace_ffi_try("Failed connecting to master listener (thread mode)", + ddog_sidecar_connect_worker(ddtrace_sidecar_master_pid, &sidecar_transport))) { + LOG(WARN, "Failed to connect to master listener"); + return NULL; + } + + char logpath[MAXPATHLEN]; + int error_fd = atomic_load(&ddtrace_error_log_fd); + if (error_fd == -1 || ddtrace_get_fd_path(error_fd, logpath) < 0) { + *logpath = 0; + } + + dd_sidecar_post_connect(&sidecar_transport, false, logpath); + + // Set active mode + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; + + return sidecar_transport; +#else + // Thread mode not supported on Windows + LOG(ERROR, "Thread-based sidecar connection is not supported on Windows"); + return NULL; +#endif +} + +// Auto-fallback connection logic +ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void) { + zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); + ddog_SidecarTransport *transport = NULL; + + switch (mode) { + case DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS: + // Force subprocess only + LOG(INFO, "Sidecar connection mode: subprocess (forced)"); + transport = ddtrace_sidecar_connect_subprocess(); + if (!transport) { + LOG(ERROR, "Subprocess connection failed (mode=subprocess, no fallback)"); + } + break; + + case DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD: + // Force thread only + LOG(INFO, "Sidecar connection mode: thread (forced)"); + transport = ddtrace_sidecar_connect_thread(); + if (!transport) { + LOG(ERROR, "Thread connection failed (mode=thread, no fallback)"); + } + break; + + case DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO: + default: + // Try subprocess first + LOG(INFO, "Sidecar connection mode: auto (trying subprocess first)"); + transport = ddtrace_sidecar_connect_subprocess(); + + if (transport) { + LOG(INFO, "Connected to sidecar via subprocess"); + } else { + // Fallback to thread mode + LOG(WARN, "Subprocess connection failed, falling back to thread mode"); + transport = ddtrace_sidecar_connect_thread(); + + if (transport) { + LOG(INFO, "Connected to sidecar via thread (fallback)"); + } else { + LOG(ERROR, "Both subprocess and thread connections failed, sidecar unavailable"); + } + } + break; + } + + return transport; +} + static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { // Should not happen, unless the agent url is malformed if (!ddtrace_endpoint) { @@ -189,7 +335,20 @@ static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { } ddog_SidecarTransport *dd_sidecar_connection_factory(void) { - return dd_sidecar_connection_factory_ex(false); + // Reconnect using the same mode that succeeded initially + switch (ddtrace_sidecar_active_mode) { + case DD_SIDECAR_CONNECTION_SUBPROCESS: + return ddtrace_sidecar_connect_subprocess(); + + case DD_SIDECAR_CONNECTION_THREAD: + return ddtrace_sidecar_connect_thread(); + + case DD_SIDECAR_CONNECTION_NONE: + default: + // Shouldn't happen, but fall back to auto mode + LOG(WARN, "Reconnection attempted with no active mode, using fallback logic"); + return ddtrace_sidecar_connect_with_fallback(); + } } bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_config) { @@ -222,7 +381,8 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_activation, appsec_config); - ddtrace_sidecar = dd_sidecar_connection_factory(); + // Use fallback connection logic + ddtrace_sidecar = ddtrace_sidecar_connect_with_fallback(); if (!ddtrace_sidecar) { // Something went wrong if (ddtrace_endpoint) { dd_free_endpoints(); @@ -234,6 +394,15 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { } } +// Initialize sidecar globals at module init +void ddtrace_sidecar_minit(void) { +#ifndef _WIN32 + if (ddtrace_sidecar_master_pid == 0) { + ddtrace_sidecar_master_pid = (int32_t)getpid(); + } +#endif +} + void ddtrace_sidecar_ensure_active(void) { if (ddtrace_sidecar) { ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory); @@ -261,8 +430,29 @@ void ddtrace_sidecar_finalize(bool clear_id) { } void ddtrace_sidecar_shutdown(void) { +#ifndef _WIN32 + // Shutdown master listener if this is the master process and thread mode is active + int32_t current_pid = (int32_t)getpid(); + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD && + ddtrace_sidecar_master_pid != 0 && + current_pid == ddtrace_sidecar_master_pid) { + + // Close worker connection first to avoid deadlock + if (ddtrace_sidecar) { + ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; + } + + // Then shutdown listener thread + ddtrace_ffi_try("Failed shutting down master listener", + ddog_sidecar_shutdown_master_listener()); + } +#endif + + // Standard cleanup if (ddtrace_sidecar_instance_id) { ddog_sidecar_instanceId_drop(ddtrace_sidecar_instance_id); + ddtrace_sidecar_instance_id = NULL; } if (ddtrace_endpoint) { @@ -271,7 +461,11 @@ void ddtrace_sidecar_shutdown(void) { if (ddtrace_sidecar) { ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; } + + // Reset mode + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_NONE; } void ddtrace_force_new_instance_id(void) { @@ -286,6 +480,19 @@ void ddtrace_reset_sidecar(void) { if (ddtrace_sidecar) { ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; + + // Don't reconnect in thread mode after fork (Option A: documented incompatibility) + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + // Sidecar unavailable in child process after fork + LOG(WARN, "Thread mode sidecar cannot be reset after fork, sidecar unavailable"); + if (ddtrace_endpoint) { + dd_free_endpoints(); + } + return; + } + + // For subprocess mode, reconnect with is_fork=true ddtrace_sidecar = dd_sidecar_connection_factory_ex(true); if (!ddtrace_sidecar) { // Something went wrong if (ddtrace_endpoint) { @@ -596,6 +803,11 @@ void ddtrace_sidecar_rinit(void) { void ddtrace_sidecar_rshutdown(void) { ddog_Vec_Tag_drop(DDTRACE_G(active_global_tags)); + + // For CLI SAPI, shut down sidecar here (before ASAN checks) + if (strcmp(sapi_module.name, "cli") == 0) { + ddtrace_sidecar_shutdown(); + } } bool ddtrace_alter_test_session_token(zval *old_value, zval *new_value, zend_string *new_str) { diff --git a/ext/sidecar.h b/ext/sidecar.h index b3593ceaf1c..d96d25fef28 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -7,9 +7,18 @@ #include "ddtrace.h" #include "zend_string.h" +// Connection mode tracking +typedef enum { + DD_SIDECAR_CONNECTION_NONE = 0, + DD_SIDECAR_CONNECTION_SUBPROCESS = 1, + DD_SIDECAR_CONNECTION_THREAD = 2 +} dd_sidecar_active_mode_t; + extern ddog_SidecarTransport *ddtrace_sidecar; extern ddog_Endpoint *ddtrace_endpoint; extern struct ddog_InstanceId *ddtrace_sidecar_instance_id; +extern dd_sidecar_active_mode_t ddtrace_sidecar_active_mode; +extern int32_t ddtrace_sidecar_master_pid; DDTRACE_PUBLIC const uint8_t *ddtrace_get_formatted_session_id(void); struct telemetry_rc_info { @@ -20,6 +29,13 @@ struct telemetry_rc_info { }; DDTRACE_PUBLIC struct telemetry_rc_info ddtrace_get_telemetry_rc_info(void); +// Connection functions +ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void); +ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void); +ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void); + +// Lifecycle functions +void ddtrace_sidecar_minit(void); void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config); bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_config); void ddtrace_sidecar_ensure_active(void); From 37ec8f2b08b68067cf86acc2426867133c437be2 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 14:11:49 +0100 Subject: [PATCH 4/6] fix: compilation error Signed-off-by: Alexandre Rulleau --- ext/coms.c | 9 ++ ext/ddtrace.c | 4 +- ext/sidecar.c | 122 +++++++----------- ext/sidecar.h | 2 +- .../Custom/Autoloaded/InstrumentationTest.php | 1 + tests/ext/sandbox/die_in_sandbox.phpt | 2 + tests/ext/span_on_close.phpt | 2 + tests/ext/startup_logging_json_config.phpt | 2 + 8 files changed, 66 insertions(+), 78 deletions(-) diff --git a/ext/coms.c b/ext/coms.c index d79e7ea4088..bae9537dac9 100644 --- a/ext/coms.c +++ b/ext/coms.c @@ -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 } } @@ -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); diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 5b4b7ed62ac..cb7b6a5f4ff 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1586,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(); diff --git a/ext/sidecar.c b/ext/sidecar.c index cddd5645375..e198084a661 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -1,3 +1,5 @@ +#include +#include
#include "ddtrace.h" #include "auto_flush.h" #include "compat_string.h" @@ -162,7 +164,6 @@ static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { } -// Subprocess connection mode - current default behavior ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void) { if (!ddtrace_endpoint) { return NULL; @@ -193,13 +194,11 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void) { dd_sidecar_post_connect(&sidecar_transport, false, logpath); - // Set active mode ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_SUBPROCESS; return sidecar_transport; } -// Thread connection mode - fallback when subprocess fails ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { if (!ddtrace_endpoint) { return NULL; @@ -211,12 +210,10 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { bool is_master = (ddtrace_sidecar_master_pid == 0 || current_pid == ddtrace_sidecar_master_pid); if (is_master) { - // Set master PID if (ddtrace_sidecar_master_pid == 0) { ddtrace_sidecar_master_pid = current_pid; } - // Start master listener thread (only if not already running) if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { if (!ddtrace_ffi_try("Failed starting master listener thread", ddog_sidecar_connect_master(ddtrace_sidecar_master_pid))) { @@ -228,7 +225,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { } } - // Connect as worker to master listener ddog_SidecarTransport *sidecar_transport; if (!ddtrace_ffi_try("Failed connecting to master listener (thread mode)", ddog_sidecar_connect_worker(ddtrace_sidecar_master_pid, &sidecar_transport))) { @@ -244,7 +240,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { dd_sidecar_post_connect(&sidecar_transport, false, logpath); - // Set active mode ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; return sidecar_transport; @@ -255,15 +250,25 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { #endif } -// Auto-fallback connection logic -ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void) { +ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { + if (is_fork && ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + LOG(WARN, "Thread mode sidecar cannot be reset after fork, sidecar unavailable"); + return NULL; + } + + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { + return ddtrace_sidecar_connect_subprocess(); + } else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + return ddtrace_sidecar_connect_thread(); + } + zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); ddog_SidecarTransport *transport = NULL; switch (mode) { case DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS: // Force subprocess only - LOG(INFO, "Sidecar connection mode: subprocess (forced)"); + LOG(DEBUG, "Sidecar connection mode: subprocess (forced)"); transport = ddtrace_sidecar_connect_subprocess(); if (!transport) { LOG(ERROR, "Subprocess connection failed (mode=subprocess, no fallback)"); @@ -272,7 +277,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void) { case DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD: // Force thread only - LOG(INFO, "Sidecar connection mode: thread (forced)"); + LOG(DEBUG, "Sidecar connection mode: thread (forced)"); transport = ddtrace_sidecar_connect_thread(); if (!transport) { LOG(ERROR, "Thread connection failed (mode=thread, no fallback)"); @@ -281,14 +286,17 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void) { case DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO: default: - // Try subprocess first - LOG(INFO, "Sidecar connection mode: auto (trying subprocess first)"); + // Try subprocess first, fallback to thread if needed + LOG(DEBUG, "Sidecar connection mode: auto (trying subprocess first)"); transport = ddtrace_sidecar_connect_subprocess(); if (transport) { - LOG(INFO, "Connected to sidecar via subprocess"); + LOG(DEBUG, "Connected to sidecar via subprocess"); + } else if (!ddtrace_endpoint) { + // Don't try fallback if endpoint is invalid - both modes need a valid endpoint + // The "Invalid DD_TRACE_AGENT_URL" error was already logged during endpoint creation } else { - // Fallback to thread mode + // Subprocess failed but endpoint is valid - try thread mode fallback LOG(WARN, "Subprocess connection failed, falling back to thread mode"); transport = ddtrace_sidecar_connect_thread(); @@ -304,51 +312,8 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void) { return transport; } -static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { - // Should not happen, unless the agent url is malformed - if (!ddtrace_endpoint) { - return NULL; - } - ZEND_ASSERT(dogstatsd_endpoint != NULL); - - dd_set_endpoint_test_token(dogstatsd_endpoint); - -#ifdef _WIN32 - DDOG_PHP_FUNCTION = (const uint8_t *)zend_hash_func; -#endif - - char logpath[MAXPATHLEN]; - int error_fd = atomic_load(&ddtrace_error_log_fd); - if (error_fd == -1 || ddtrace_get_fd_path(error_fd, logpath) < 0) { - *logpath = 0; - } - - ddog_SidecarTransport *sidecar_transport; - if (!ddtrace_ffi_try("Failed connecting to the sidecar", ddog_sidecar_connect_php(&sidecar_transport, logpath, dd_zend_string_to_CharSlice(get_global_DD_TRACE_LOG_LEVEL()), get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), dd_sidecar_on_reconnect, ddtrace_endpoint))) { - dd_free_endpoints(); - return NULL; - } - - dd_sidecar_post_connect(&sidecar_transport, is_fork, logpath); - - return sidecar_transport; -} - -ddog_SidecarTransport *dd_sidecar_connection_factory(void) { - // Reconnect using the same mode that succeeded initially - switch (ddtrace_sidecar_active_mode) { - case DD_SIDECAR_CONNECTION_SUBPROCESS: - return ddtrace_sidecar_connect_subprocess(); - - case DD_SIDECAR_CONNECTION_THREAD: - return ddtrace_sidecar_connect_thread(); - - case DD_SIDECAR_CONNECTION_NONE: - default: - // Shouldn't happen, but fall back to auto mode - LOG(WARN, "Reconnection attempted with no active mode, using fallback logic"); - return ddtrace_sidecar_connect_with_fallback(); - } +static ddog_SidecarTransport *ddtrace_sidecar_connect_callback(void) { + return ddtrace_sidecar_connect(false); } bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_config) { @@ -381,8 +346,7 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_activation, appsec_config); - // Use fallback connection logic - ddtrace_sidecar = ddtrace_sidecar_connect_with_fallback(); + ddtrace_sidecar = ddtrace_sidecar_connect(false); if (!ddtrace_sidecar) { // Something went wrong if (ddtrace_endpoint) { dd_free_endpoints(); @@ -405,7 +369,7 @@ void ddtrace_sidecar_minit(void) { void ddtrace_sidecar_ensure_active(void) { if (ddtrace_sidecar) { - ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory); + ddtrace_sidecar_reconnect(&ddtrace_sidecar, ddtrace_sidecar_connect_callback); } } @@ -482,19 +446,8 @@ void ddtrace_reset_sidecar(void) { ddog_sidecar_transport_drop(ddtrace_sidecar); ddtrace_sidecar = NULL; - // Don't reconnect in thread mode after fork (Option A: documented incompatibility) - if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { - // Sidecar unavailable in child process after fork - LOG(WARN, "Thread mode sidecar cannot be reset after fork, sidecar unavailable"); - if (ddtrace_endpoint) { - dd_free_endpoints(); - } - return; - } - - // For subprocess mode, reconnect with is_fork=true - ddtrace_sidecar = dd_sidecar_connection_factory_ex(true); - if (!ddtrace_sidecar) { // Something went wrong + ddtrace_sidecar = ddtrace_sidecar_connect(true); + if (!ddtrace_sidecar) { if (ddtrace_endpoint) { dd_free_endpoints(); } @@ -805,7 +758,24 @@ void ddtrace_sidecar_rshutdown(void) { ddog_Vec_Tag_drop(DDTRACE_G(active_global_tags)); // For CLI SAPI, shut down sidecar here (before ASAN checks) + // CRITICAL: Must shut down background sender FIRST if it's running, + // otherwise it may try to access instance_id while we're freeing it if (strcmp(sapi_module.name, "cli") == 0) { +#ifndef _WIN32 + if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) { + // Background sender is running - must shut it down first + extern void ddtrace_coms_mshutdown(void); + extern bool ddtrace_coms_flush_shutdown_writer_synchronous(void); + extern void ddtrace_coms_curl_shutdown(void); + extern void ddtrace_coms_mshutdown_proxy_env(void); + + ddtrace_coms_mshutdown(); + if (ddtrace_coms_flush_shutdown_writer_synchronous()) { + ddtrace_coms_curl_shutdown(); + } + ddtrace_coms_mshutdown_proxy_env(); + } +#endif ddtrace_sidecar_shutdown(); } } diff --git a/ext/sidecar.h b/ext/sidecar.h index d96d25fef28..26b01b7c0ea 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -32,7 +32,7 @@ DDTRACE_PUBLIC struct telemetry_rc_info ddtrace_get_telemetry_rc_info(void); // Connection functions ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void); ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void); -ddog_SidecarTransport *ddtrace_sidecar_connect_with_fallback(void); +ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork); // Lifecycle functions void ddtrace_sidecar_minit(void); diff --git a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php index eb85ee4a56e..d42cfcc4e50 100644 --- a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php +++ b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php @@ -23,6 +23,7 @@ protected static function getEnvs() 'DD_AGENT_HOST' => 'request-replayer', 'DD_INSTRUMENTATION_TELEMETRY_ENABLED' => 1, 'DD_LOGS_INJECTION' => 'false', + 'DD_TRACE_DEBUG' => 1, // Enable DEBUG logs so they're counted in logs_created metric ]); } diff --git a/tests/ext/sandbox/die_in_sandbox.phpt b/tests/ext/sandbox/die_in_sandbox.phpt index 318f0467532..5c2da8f16fa 100644 --- a/tests/ext/sandbox/die_in_sandbox.phpt +++ b/tests/ext/sandbox/die_in_sandbox.phpt @@ -17,6 +17,8 @@ x(); ?> --EXPECTF-- +[ddtrace] [debug] Sidecar connection mode: auto (trying subprocess first) +[ddtrace] [debug] Connected to sidecar via subprocess [ddtrace] [warning] UnwindExit thrown in ddtrace's closure defined at %s:%d for x(): in Unknown on line 0 [ddtrace] [span] Encoding span: Span { service: die_in_sandbox.php, name: die_in_sandbox.php, resource: die_in_sandbox.php, type: cli, trace_id: %d, span_id: %d, parent_id: %d, start: %d, duration: %d, error: %d, meta: %s, metrics: %s, meta_struct: %s, span_links: %s, span_events: %s } [ddtrace] [span] Encoding span: Span { service: die_in_sandbox.php, name: x, resource: x, type: cli, trace_id: %d, span_id: %d, parent_id: %d, start: %d, duration: %d, error: %d, meta: %s, metrics: %s, meta_struct: %s, span_links: %s, span_events: %s } diff --git a/tests/ext/span_on_close.phpt b/tests/ext/span_on_close.phpt index ce519454054..c944b7d6b21 100644 --- a/tests/ext/span_on_close.phpt +++ b/tests/ext/span_on_close.phpt @@ -24,6 +24,8 @@ $span->onClose = [ ?> --EXPECTF-- +[ddtrace] [debug] Sidecar connection mode: auto (trying subprocess first) +[ddtrace] [debug] Connected to sidecar via subprocess Second First [ddtrace] [span] Encoding span: Span { service: %s, name: root span, resource: root span, type: cli, trace_id: %d, span_id: %d, parent_id: %d, start: %d, duration: %d, error: %d, meta: %s, metrics: %s, meta_struct: %s, span_links: %s, span_events: %s } diff --git a/tests/ext/startup_logging_json_config.phpt b/tests/ext/startup_logging_json_config.phpt index 62b7110518e..6113ec85384 100644 --- a/tests/ext/startup_logging_json_config.phpt +++ b/tests/ext/startup_logging_json_config.phpt @@ -52,6 +52,8 @@ dd_dump_startup_logs($logs, [ ]); ?> --EXPECT-- +[ddtrace] [debug] Sidecar connection mode: auto (trying subprocess first) +[ddtrace] [debug] Connected to sidecar via subprocess Sanity check env: "my-env" service: "my-service" From cda73e8ca25431d81f31615ea941175acc8434e6 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Fri, 16 Jan 2026 13:06:18 +0100 Subject: [PATCH 5/6] fix(tracer): remove debug logs Signed-off-by: Alexandre Rulleau --- components-rs/common.h | 280 +++++++++++---------- components-rs/crashtracker.h | 6 +- components-rs/ddtrace.h | 10 - components-rs/sidecar.h | 57 +++-- components-rs/telemetry.h | 3 +- ext/sidecar.c | 29 +-- tests/ext/sandbox/die_in_sandbox.phpt | 2 - tests/ext/span_on_close.phpt | 2 - tests/ext/startup_logging_json_config.phpt | 2 - 9 files changed, 212 insertions(+), 179 deletions(-) diff --git a/components-rs/common.h b/components-rs/common.h index bfe1502bce3..43e6a195f62 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -264,15 +264,19 @@ typedef struct _zend_string _zend_string; #define ddog_MultiTargetFetcher_DEFAULT_CLIENTS_LIMIT 100 -typedef enum ddog_ConfigurationOrigin { - DDOG_CONFIGURATION_ORIGIN_ENV_VAR, - DDOG_CONFIGURATION_ORIGIN_CODE, - DDOG_CONFIGURATION_ORIGIN_DD_CONFIG, - DDOG_CONFIGURATION_ORIGIN_REMOTE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_DEFAULT, - DDOG_CONFIGURATION_ORIGIN_LOCAL_STABLE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_FLEET_STABLE_CONFIG, -} ddog_ConfigurationOrigin; +typedef enum ddog_Log { + DDOG_LOG_ERROR = 1, + DDOG_LOG_WARN = 2, + DDOG_LOG_INFO = 3, + DDOG_LOG_DEBUG = 4, + DDOG_LOG_TRACE = 5, + DDOG_LOG_DEPRECATED = (3 | ddog_LOG_ONCE), + DDOG_LOG_STARTUP = (3 | (2 << 4)), + DDOG_LOG_STARTUP_WARN = (1 | (2 << 4)), + DDOG_LOG_SPAN = (4 | (3 << 4)), + DDOG_LOG_SPAN_TRACE = (5 | (3 << 4)), + DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), +} ddog_Log; typedef enum ddog_DynamicConfigUpdateMode { DDOG_DYNAMIC_CONFIG_UPDATE_MODE_READ, @@ -281,30 +285,16 @@ typedef enum ddog_DynamicConfigUpdateMode { DDOG_DYNAMIC_CONFIG_UPDATE_MODE_RESTORE, } ddog_DynamicConfigUpdateMode; -typedef enum ddog_EvaluateAt { - DDOG_EVALUATE_AT_ENTRY, - DDOG_EVALUATE_AT_EXIT, -} ddog_EvaluateAt; - typedef enum ddog_InBodyLocation { DDOG_IN_BODY_LOCATION_NONE, DDOG_IN_BODY_LOCATION_START, DDOG_IN_BODY_LOCATION_END, } ddog_InBodyLocation; -typedef enum ddog_Log { - DDOG_LOG_ERROR = 1, - DDOG_LOG_WARN = 2, - DDOG_LOG_INFO = 3, - DDOG_LOG_DEBUG = 4, - DDOG_LOG_TRACE = 5, - DDOG_LOG_DEPRECATED = (3 | ddog_LOG_ONCE), - DDOG_LOG_STARTUP = (3 | (2 << 4)), - DDOG_LOG_STARTUP_WARN = (1 | (2 << 4)), - DDOG_LOG_SPAN = (4 | (3 << 4)), - DDOG_LOG_SPAN_TRACE = (5 | (3 << 4)), - DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), -} ddog_Log; +typedef enum ddog_EvaluateAt { + DDOG_EVALUATE_AT_ENTRY, + DDOG_EVALUATE_AT_EXIT, +} ddog_EvaluateAt; typedef enum ddog_MetricKind { DDOG_METRIC_KIND_COUNT, @@ -313,6 +303,37 @@ typedef enum ddog_MetricKind { DDOG_METRIC_KIND_DISTRIBUTION, } ddog_MetricKind; +typedef enum ddog_SpanProbeTarget { + DDOG_SPAN_PROBE_TARGET_ACTIVE, + DDOG_SPAN_PROBE_TARGET_ROOT, +} ddog_SpanProbeTarget; + +typedef enum ddog_ProbeStatus { + DDOG_PROBE_STATUS_RECEIVED, + DDOG_PROBE_STATUS_INSTALLED, + DDOG_PROBE_STATUS_EMITTING, + DDOG_PROBE_STATUS_ERROR, + DDOG_PROBE_STATUS_BLOCKED, + DDOG_PROBE_STATUS_WARNING, +} ddog_ProbeStatus; + +typedef enum ddog_ConfigurationOrigin { + DDOG_CONFIGURATION_ORIGIN_ENV_VAR, + DDOG_CONFIGURATION_ORIGIN_CODE, + DDOG_CONFIGURATION_ORIGIN_DD_CONFIG, + DDOG_CONFIGURATION_ORIGIN_REMOTE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_DEFAULT, + DDOG_CONFIGURATION_ORIGIN_LOCAL_STABLE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_FLEET_STABLE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_CALCULATED, +} ddog_ConfigurationOrigin; + +typedef enum ddog_MetricType { + DDOG_METRIC_TYPE_GAUGE, + DDOG_METRIC_TYPE_COUNT, + DDOG_METRIC_TYPE_DISTRIBUTION, +} ddog_MetricType; + typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_TRACERS, DDOG_METRIC_NAMESPACE_PROFILERS, @@ -327,20 +348,16 @@ typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_SIDECAR, } ddog_MetricNamespace; -typedef enum ddog_MetricType { - DDOG_METRIC_TYPE_GAUGE, - DDOG_METRIC_TYPE_COUNT, - DDOG_METRIC_TYPE_DISTRIBUTION, -} ddog_MetricType; - -typedef enum ddog_ProbeStatus { - DDOG_PROBE_STATUS_RECEIVED, - DDOG_PROBE_STATUS_INSTALLED, - DDOG_PROBE_STATUS_EMITTING, - DDOG_PROBE_STATUS_ERROR, - DDOG_PROBE_STATUS_BLOCKED, - DDOG_PROBE_STATUS_WARNING, -} ddog_ProbeStatus; +typedef enum ddog_RemoteConfigProduct { + DDOG_REMOTE_CONFIG_PRODUCT_AGENT_CONFIG, + DDOG_REMOTE_CONFIG_PRODUCT_AGENT_TASK, + DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, + DDOG_REMOTE_CONFIG_PRODUCT_ASM, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, + DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, +} ddog_RemoteConfigProduct; typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_ACTIVATION = 1, @@ -388,22 +405,6 @@ typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_TRACE_TAGGING_RULES = 43, } ddog_RemoteConfigCapabilities; -typedef enum ddog_RemoteConfigProduct { - DDOG_REMOTE_CONFIG_PRODUCT_AGENT_CONFIG, - DDOG_REMOTE_CONFIG_PRODUCT_AGENT_TASK, - DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, - DDOG_REMOTE_CONFIG_PRODUCT_ASM, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, - DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, -} ddog_RemoteConfigProduct; - -typedef enum ddog_SpanProbeTarget { - DDOG_SPAN_PROBE_TARGET_ACTIVE, - DDOG_SPAN_PROBE_TARGET_ROOT, -} ddog_SpanProbeTarget; - typedef struct ddog_DebuggerPayload ddog_DebuggerPayload; typedef struct ddog_DslString ddog_DslString; @@ -774,17 +775,18 @@ typedef struct ddog_DebuggerValue ddog_DebuggerValue; #define ddog_EVALUATOR_RESULT_REDACTED (const void*)-2 -typedef enum ddog_DebuggerType { - DDOG_DEBUGGER_TYPE_DIAGNOSTICS, - DDOG_DEBUGGER_TYPE_LOGS, -} ddog_DebuggerType; - typedef enum ddog_FieldType { DDOG_FIELD_TYPE_STATIC, DDOG_FIELD_TYPE_ARG, DDOG_FIELD_TYPE_LOCAL, } ddog_FieldType; +typedef enum ddog_DebuggerType { + DDOG_DEBUGGER_TYPE_DIAGNOSTICS, + DDOG_DEBUGGER_TYPE_SNAPSHOTS, + DDOG_DEBUGGER_TYPE_LOGS, +} ddog_DebuggerType; + typedef struct ddog_Entry ddog_Entry; typedef struct ddog_HashMap_CowStr__Value ddog_HashMap_CowStr__Value; @@ -913,16 +915,6 @@ typedef struct ddog_OwnedCharSlice { void (*free)(ddog_CharSlice); } ddog_OwnedCharSlice; -typedef enum ddog_LogLevel { - DDOG_LOG_LEVEL_ERROR, - DDOG_LOG_LEVEL_WARN, - DDOG_LOG_LEVEL_DEBUG, -} ddog_LogLevel; - -typedef enum ddog_TelemetryWorkerBuilderBoolProperty { - DDOG_TELEMETRY_WORKER_BUILDER_BOOL_PROPERTY_CONFIG_TELEMETRY_DEBUG_LOGGING_ENABLED, -} ddog_TelemetryWorkerBuilderBoolProperty; - typedef enum ddog_TelemetryWorkerBuilderEndpointProperty { DDOG_TELEMETRY_WORKER_BUILDER_ENDPOINT_PROPERTY_CONFIG_ENDPOINT, } ddog_TelemetryWorkerBuilderEndpointProperty; @@ -941,6 +933,16 @@ typedef enum ddog_TelemetryWorkerBuilderStrProperty { DDOG_TELEMETRY_WORKER_BUILDER_STR_PROPERTY_RUNTIME_ID, } ddog_TelemetryWorkerBuilderStrProperty; +typedef enum ddog_TelemetryWorkerBuilderBoolProperty { + DDOG_TELEMETRY_WORKER_BUILDER_BOOL_PROPERTY_CONFIG_TELEMETRY_DEBUG_LOGGING_ENABLED, +} ddog_TelemetryWorkerBuilderBoolProperty; + +typedef enum ddog_LogLevel { + DDOG_LOG_LEVEL_ERROR, + DDOG_LOG_LEVEL_WARN, + DDOG_LOG_LEVEL_DEBUG, +} ddog_LogLevel; + typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder; /** @@ -954,6 +956,20 @@ typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder; */ typedef struct ddog_TelemetryWorkerHandle ddog_TelemetryWorkerHandle; +typedef enum ddog_Option_U64_Tag { + DDOG_OPTION_U64_SOME_U64, + DDOG_OPTION_U64_NONE_U64, +} ddog_Option_U64_Tag; + +typedef struct ddog_Option_U64 { + ddog_Option_U64_Tag tag; + union { + struct { + uint64_t some; + }; + }; +} ddog_Option_U64; + typedef enum ddog_Option_Bool_Tag { DDOG_OPTION_BOOL_SOME_BOOL, DDOG_OPTION_BOOL_NONE_BOOL, @@ -1065,37 +1081,28 @@ typedef struct ddog_SenderParameters { ddog_CharSlice url; } ddog_SenderParameters; -typedef enum ddog_crasht_BuildIdType { - DDOG_CRASHT_BUILD_ID_TYPE_GNU, - DDOG_CRASHT_BUILD_ID_TYPE_GO, - DDOG_CRASHT_BUILD_ID_TYPE_PDB, - DDOG_CRASHT_BUILD_ID_TYPE_SHA1, -} ddog_crasht_BuildIdType; - /** - * Result type for runtime callback registration + * Stacktrace collection occurs in the context of a crashing process. + * If the stack is sufficiently corruputed, it is possible (but unlikely), + * for stack trace collection itself to crash. + * We recommend fully enabling stacktrace collection, but having an environment + * variable to allow downgrading the collector. */ -typedef enum ddog_crasht_CallbackResult { - DDOG_CRASHT_CALLBACK_RESULT_OK, - DDOG_CRASHT_CALLBACK_RESULT_ERROR, -} ddog_crasht_CallbackResult; - -typedef enum ddog_crasht_DemangleOptions { - DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, - DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, -} ddog_crasht_DemangleOptions; - -typedef enum ddog_crasht_ErrorKind { - DDOG_CRASHT_ERROR_KIND_PANIC, - DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION, - DDOG_CRASHT_ERROR_KIND_UNIX_SIGNAL, -} ddog_crasht_ErrorKind; - -typedef enum ddog_crasht_FileType { - DDOG_CRASHT_FILE_TYPE_APK, - DDOG_CRASHT_FILE_TYPE_ELF, - DDOG_CRASHT_FILE_TYPE_PE, -} ddog_crasht_FileType; +typedef enum ddog_crasht_StacktraceCollection { + /** + * Stacktrace collection occurs in the + */ + DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, + DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, + /** + * This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information + * and also unwind inlined functions. Enabling this feature will not only provide symbolic + * details, but may also yield additional or less stack frames compared to other + * configurations. + */ + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, +} ddog_crasht_StacktraceCollection; /** * This enum represents operations a the tracked library might be engaged in. @@ -1120,6 +1127,12 @@ typedef enum ddog_crasht_OpTypes { DDOG_CRASHT_OP_TYPES_SIZE, } ddog_crasht_OpTypes; +typedef enum ddog_crasht_ErrorKind { + DDOG_CRASHT_ERROR_KIND_PANIC, + DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION, + DDOG_CRASHT_ERROR_KIND_UNIX_SIGNAL, +} ddog_crasht_ErrorKind; + /** * See https://man7.org/linux/man-pages/man2/sigaction.2.html * MUST REMAIN IN SYNC WITH THE ENUM IN emit_sigcodes.c @@ -1192,28 +1205,31 @@ typedef enum ddog_crasht_SignalNames { DDOG_CRASHT_SIGNAL_NAMES_UNKNOWN, } ddog_crasht_SignalNames; +typedef enum ddog_crasht_BuildIdType { + DDOG_CRASHT_BUILD_ID_TYPE_GNU, + DDOG_CRASHT_BUILD_ID_TYPE_GO, + DDOG_CRASHT_BUILD_ID_TYPE_PDB, + DDOG_CRASHT_BUILD_ID_TYPE_SHA1, +} ddog_crasht_BuildIdType; + +typedef enum ddog_crasht_FileType { + DDOG_CRASHT_FILE_TYPE_APK, + DDOG_CRASHT_FILE_TYPE_ELF, + DDOG_CRASHT_FILE_TYPE_PE, +} ddog_crasht_FileType; + +typedef enum ddog_crasht_DemangleOptions { + DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, + DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, +} ddog_crasht_DemangleOptions; + /** - * Stacktrace collection occurs in the context of a crashing process. - * If the stack is sufficiently corruputed, it is possible (but unlikely), - * for stack trace collection itself to crash. - * We recommend fully enabling stacktrace collection, but having an environment - * variable to allow downgrading the collector. + * Result type for runtime callback registration */ -typedef enum ddog_crasht_StacktraceCollection { - /** - * Stacktrace collection occurs in the - */ - DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, - DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, - /** - * This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information - * and also unwind inlined functions. Enabling this feature will not only provide symbolic - * details, but may also yield additional or less stack frames compared to other - * configurations. - */ - DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, - DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, -} ddog_crasht_StacktraceCollection; +typedef enum ddog_crasht_CallbackResult { + DDOG_CRASHT_CALLBACK_RESULT_OK, + DDOG_CRASHT_CALLBACK_RESULT_ERROR, +} ddog_crasht_CallbackResult; typedef struct ddog_crasht_CrashInfo ddog_crasht_CrashInfo; @@ -1291,7 +1307,7 @@ typedef struct ddog_crasht_Config { /** * Timeout in milliseconds before the signal handler starts tearing things down to return. * If 0, uses the default timeout as specified in - * `datadog_crashtracker::shared::constants::DD_CRASHTRACK_DEFAULT_TIMEOUT`. Otherwise, uses + * `libdd_crashtracker::shared::constants::DD_CRASHTRACK_DEFAULT_TIMEOUT`. Otherwise, uses * the specified timeout value. * This is given as a uint32_t, but the actual timeout needs to fit inside of an i32 (max * 2^31-1). This is a limitation of the various interfaces used to guarantee the timeout. @@ -1411,13 +1427,17 @@ typedef struct ddog_crasht_CrashInfoBuilder_NewResult { }; } ddog_crasht_CrashInfoBuilder_NewResult; -typedef enum ddog_crasht_CrashInfo_NewResult_Tag { - DDOG_CRASHT_CRASH_INFO_NEW_RESULT_OK, - DDOG_CRASHT_CRASH_INFO_NEW_RESULT_ERR, -} ddog_crasht_CrashInfo_NewResult_Tag; +/** + * A generic result type for when an operation may fail, + * or may return in case of success. + */ +typedef enum ddog_crasht_Result_HandleCrashInfo_Tag { + DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_OK_HANDLE_CRASH_INFO, + DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_ERR_HANDLE_CRASH_INFO, +} ddog_crasht_Result_HandleCrashInfo_Tag; -typedef struct ddog_crasht_CrashInfo_NewResult { - ddog_crasht_CrashInfo_NewResult_Tag tag; +typedef struct ddog_crasht_Result_HandleCrashInfo { + ddog_crasht_Result_HandleCrashInfo_Tag tag; union { struct { struct ddog_crasht_Handle_CrashInfo ok; @@ -1426,7 +1446,9 @@ typedef struct ddog_crasht_CrashInfo_NewResult { struct ddog_Error err; }; }; -} ddog_crasht_CrashInfo_NewResult; +} ddog_crasht_Result_HandleCrashInfo; + +typedef struct ddog_crasht_Result_HandleCrashInfo ddog_crasht_CrashInfo_NewResult; typedef struct ddog_crasht_OsInfo { ddog_CharSlice architecture; diff --git a/components-rs/crashtracker.h b/components-rs/crashtracker.h index 42209ff18cc..96ed333d622 100644 --- a/components-rs/crashtracker.h +++ b/components-rs/crashtracker.h @@ -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 @@ -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 */ @@ -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 diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index 6fe1ee39216..35d06a61820 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -116,16 +116,6 @@ ddog_MaybeError ddog_sidecar_connect_php(struct ddog_SidecarTransport **connecti void ddtrace_sidecar_reconnect(struct ddog_SidecarTransport **transport, struct ddog_SidecarTransport *(*factory)(void)); -// Thread-based sidecar connection (Unix only) -#if !defined(_WIN32) -ddog_MaybeError ddog_sidecar_connect_master(int32_t pid); -ddog_MaybeError ddog_sidecar_connect_worker(int32_t pid, - struct ddog_SidecarTransport **connection); -ddog_MaybeError ddog_sidecar_shutdown_master_listener(void); -bool ddog_sidecar_is_master_listener_active(int32_t pid); -ddog_MaybeError ddog_sidecar_clear_inherited_listener(void); -#endif - bool ddog_shm_limiter_inc(const struct ddog_MaybeShmLimiter *limiter, uint32_t limit); bool ddog_exception_hash_limiter_inc(struct ddog_SidecarTransport *connection, diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 2701a73cd8e..1115054108b 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -95,40 +95,68 @@ 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. + * 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 */ -#if !defined(_WIN32) ddog_MaybeError ddog_sidecar_connect_master(int32_t pid); -#endif /** * 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 */ -#if !defined(_WIN32) -ddog_MaybeError ddog_sidecar_connect_worker(int32_t pid, - struct ddog_SidecarTransport **connection); -#endif +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 */ -#if !defined(_WIN32) ddog_MaybeError ddog_sidecar_shutdown_master_listener(void); -#endif /** * 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 */ -#if !defined(_WIN32) bool ddog_sidecar_is_master_listener_active(int32_t pid); -#endif /** * 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 */ -#if !defined(_WIN32) ddog_MaybeError ddog_sidecar_clear_inherited_listener(void); -#endif ddog_MaybeError ddog_sidecar_ping(struct ddog_SidecarTransport **transport); @@ -156,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. diff --git a/components-rs/telemetry.h b/components-rs/telemetry.h index fd8ffadcdbf..5a6c6288ece 100644 --- a/components-rs/telemetry.h +++ b/components-rs/telemetry.h @@ -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 diff --git a/ext/sidecar.c b/ext/sidecar.c index e198084a661..0d2ae7a9c1e 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -268,7 +268,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { switch (mode) { case DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS: // Force subprocess only - LOG(DEBUG, "Sidecar connection mode: subprocess (forced)"); transport = ddtrace_sidecar_connect_subprocess(); if (!transport) { LOG(ERROR, "Subprocess connection failed (mode=subprocess, no fallback)"); @@ -277,7 +276,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { case DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD: // Force thread only - LOG(DEBUG, "Sidecar connection mode: thread (forced)"); transport = ddtrace_sidecar_connect_thread(); if (!transport) { LOG(ERROR, "Thread connection failed (mode=thread, no fallback)"); @@ -287,23 +285,22 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { case DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO: default: // Try subprocess first, fallback to thread if needed - LOG(DEBUG, "Sidecar connection mode: auto (trying subprocess first)"); transport = ddtrace_sidecar_connect_subprocess(); - if (transport) { - LOG(DEBUG, "Connected to sidecar via subprocess"); - } else if (!ddtrace_endpoint) { - // Don't try fallback if endpoint is invalid - both modes need a valid endpoint - // The "Invalid DD_TRACE_AGENT_URL" error was already logged during endpoint creation - } else { - // Subprocess failed but endpoint is valid - try thread mode fallback - LOG(WARN, "Subprocess connection failed, falling back to thread mode"); - transport = ddtrace_sidecar_connect_thread(); - - if (transport) { - LOG(INFO, "Connected to sidecar via thread (fallback)"); + if (!transport) { + if (!ddtrace_endpoint) { + // Don't try fallback if endpoint is invalid - both modes need a valid endpoint + // The "Invalid DD_TRACE_AGENT_URL" error was already logged during endpoint creation } else { - LOG(ERROR, "Both subprocess and thread connections failed, sidecar unavailable"); + // Subprocess failed but endpoint is valid - try thread mode fallback + LOG(WARN, "Subprocess connection failed, falling back to thread mode"); + transport = ddtrace_sidecar_connect_thread(); + + if (transport) { + LOG(INFO, "Connected to sidecar via thread (fallback)"); + } else { + LOG(ERROR, "Both subprocess and thread connections failed, sidecar unavailable"); + } } } break; diff --git a/tests/ext/sandbox/die_in_sandbox.phpt b/tests/ext/sandbox/die_in_sandbox.phpt index 5c2da8f16fa..318f0467532 100644 --- a/tests/ext/sandbox/die_in_sandbox.phpt +++ b/tests/ext/sandbox/die_in_sandbox.phpt @@ -17,8 +17,6 @@ x(); ?> --EXPECTF-- -[ddtrace] [debug] Sidecar connection mode: auto (trying subprocess first) -[ddtrace] [debug] Connected to sidecar via subprocess [ddtrace] [warning] UnwindExit thrown in ddtrace's closure defined at %s:%d for x(): in Unknown on line 0 [ddtrace] [span] Encoding span: Span { service: die_in_sandbox.php, name: die_in_sandbox.php, resource: die_in_sandbox.php, type: cli, trace_id: %d, span_id: %d, parent_id: %d, start: %d, duration: %d, error: %d, meta: %s, metrics: %s, meta_struct: %s, span_links: %s, span_events: %s } [ddtrace] [span] Encoding span: Span { service: die_in_sandbox.php, name: x, resource: x, type: cli, trace_id: %d, span_id: %d, parent_id: %d, start: %d, duration: %d, error: %d, meta: %s, metrics: %s, meta_struct: %s, span_links: %s, span_events: %s } diff --git a/tests/ext/span_on_close.phpt b/tests/ext/span_on_close.phpt index c944b7d6b21..ce519454054 100644 --- a/tests/ext/span_on_close.phpt +++ b/tests/ext/span_on_close.phpt @@ -24,8 +24,6 @@ $span->onClose = [ ?> --EXPECTF-- -[ddtrace] [debug] Sidecar connection mode: auto (trying subprocess first) -[ddtrace] [debug] Connected to sidecar via subprocess Second First [ddtrace] [span] Encoding span: Span { service: %s, name: root span, resource: root span, type: cli, trace_id: %d, span_id: %d, parent_id: %d, start: %d, duration: %d, error: %d, meta: %s, metrics: %s, meta_struct: %s, span_links: %s, span_events: %s } diff --git a/tests/ext/startup_logging_json_config.phpt b/tests/ext/startup_logging_json_config.phpt index 6113ec85384..62b7110518e 100644 --- a/tests/ext/startup_logging_json_config.phpt +++ b/tests/ext/startup_logging_json_config.phpt @@ -52,8 +52,6 @@ dd_dump_startup_logs($logs, [ ]); ?> --EXPECT-- -[ddtrace] [debug] Sidecar connection mode: auto (trying subprocess first) -[ddtrace] [debug] Connected to sidecar via subprocess Sanity check env: "my-env" service: "my-service" From fc9fcbf0693109827581a3e6fbd07efe4ad0fdde Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Mon, 19 Jan 2026 17:24:45 +0100 Subject: [PATCH 6/6] feat(tracer): add connection mode and function thread mode tests Signed-off-by: Alexandre Rulleau --- ext/sidecar.c | 168 +++++++++--- tests/Common/WebFrameworkTestCase.php | 2 +- .../Autoloaded/SidecarThreadModeTest.php | 91 +++++++ tests/Sapi/PhpFpm/PhpFpm.php | 21 +- tests/Sapi/PhpFpm/www.conf | 2 +- tests/WebServer.php | 9 +- tests/ext/sidecar_connection_mode_auto.phpt | 24 ++ tests/ext/sidecar_connection_mode_config.phpt | 16 ++ .../sidecar_connection_mode_fork_warning.phpt | 38 +++ .../ext/sidecar_connection_mode_invalid.phpt | 24 ++ .../sidecar_connection_mode_subprocess.phpt | 24 ++ tests/ext/sidecar_connection_mode_thread.phpt | 31 +++ ...car_connection_mode_thread_functional.phpt | 98 +++++++ ...sidecar_connection_mode_thread_phpfpm.phpt | 243 ++++++++++++++++++ 14 files changed, 753 insertions(+), 38 deletions(-) create mode 100644 tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php create mode 100644 tests/ext/sidecar_connection_mode_auto.phpt create mode 100644 tests/ext/sidecar_connection_mode_config.phpt create mode 100644 tests/ext/sidecar_connection_mode_fork_warning.phpt create mode 100644 tests/ext/sidecar_connection_mode_invalid.phpt create mode 100644 tests/ext/sidecar_connection_mode_subprocess.phpt create mode 100644 tests/ext/sidecar_connection_mode_thread.phpt create mode 100644 tests/ext/sidecar_connection_mode_thread_functional.phpt create mode 100644 tests/ext/sidecar_connection_mode_thread_phpfpm.phpt diff --git a/ext/sidecar.c b/ext/sidecar.c index 0d2ae7a9c1e..26155f4b417 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -121,6 +121,10 @@ static void dd_sidecar_post_connect(ddog_SidecarTransport **transport, bool is_f } } +static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork); +static ddog_SidecarTransport *dd_sidecar_connection_factory_ex_non_fork(void); +static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_config); + static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { if (!ddtrace_endpoint || !dogstatsd_endpoint) { return; @@ -199,37 +203,116 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void) { return sidecar_transport; } -ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { +static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { + // Should not happen, unless the agent url is malformed if (!ddtrace_endpoint) { return NULL; } ZEND_ASSERT(dogstatsd_endpoint != NULL); + dd_set_endpoint_test_token(dogstatsd_endpoint); + +#ifdef _WIN32 + DDOG_PHP_FUNCTION = (const uint8_t *)zend_hash_func; +#endif + + char logpath[MAXPATHLEN]; + int error_fd = atomic_load(&ddtrace_error_log_fd); + if (error_fd == -1 || ddtrace_get_fd_path(error_fd, logpath) < 0) { + *logpath = 0; + } + + ddog_SidecarTransport *sidecar_transport; + if (!ddtrace_ffi_try("Failed connecting to sidecar as worker", + ddog_sidecar_connect_worker((int32_t)ddtrace_sidecar_master_pid, &sidecar_transport))) { + dd_free_endpoints(); + return NULL; + } + + dd_sidecar_post_connect(&sidecar_transport, is_fork, logpath); + + ddtrace_sidecar_reconnect(&sidecar_transport, dd_sidecar_connection_factory_ex_non_fork); + + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; + + return sidecar_transport; +} + +static ddog_SidecarTransport *dd_sidecar_connection_factory_ex_non_fork(void) { + return dd_sidecar_connection_factory_ex(false); +} + +static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_config) { #ifndef _WIN32 - int32_t current_pid = (int32_t)getpid(); - bool is_master = (ddtrace_sidecar_master_pid == 0 || current_pid == ddtrace_sidecar_master_pid); + pid_t current_pid = getpid(); + bool is_child_process = (ddtrace_sidecar_master_pid != 0 && (int32_t)current_pid != ddtrace_sidecar_master_pid); + + bool listener_available = ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid); + + if (is_child_process || listener_available) { + ddtrace_set_non_resettable_sidecar_globals(); + ddtrace_set_resettable_sidecar_globals(); + + ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_activation, appsec_config); - if (is_master) { - if (ddtrace_sidecar_master_pid == 0) { - ddtrace_sidecar_master_pid = current_pid; + if (!ddtrace_endpoint) { + LOG(WARN, "Cannot connect to sidecar: endpoint not available"); + return; } - if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { - if (!ddtrace_ffi_try("Failed starting master listener thread", - ddog_sidecar_connect_master(ddtrace_sidecar_master_pid))) { - LOG(WARN, "Failed to start master listener thread"); - return NULL; - } + ddog_SidecarTransport *sidecar_transport = NULL; + if (!ddtrace_ffi_try("Failed connecting worker to sidecar", + ddog_sidecar_connect_worker((int32_t)ddtrace_sidecar_master_pid, &sidecar_transport))) { + LOG(WARN, "Failed to connect worker to sidecar (PID=%d, master=%d)", + (int32_t)current_pid, ddtrace_sidecar_master_pid); + return; + } + + char logpath[MAXPATHLEN]; + int error_fd = atomic_load(&ddtrace_error_log_fd); + if (error_fd == -1 || ddtrace_get_fd_path(error_fd, logpath) < 0) { + *logpath = 0; + } + + dd_sidecar_post_connect(&sidecar_transport, false, logpath); + ddtrace_sidecar = sidecar_transport; - LOG(INFO, "Started master listener thread (PID=%d)", ddtrace_sidecar_master_pid); + ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_ex_non_fork); + + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; + + if (is_child_process) { + LOG(INFO, "Worker connected to sidecar master listener (worker PID=%d, master PID=%d)", + (int32_t)current_pid, ddtrace_sidecar_master_pid); } + return; } - ddog_SidecarTransport *sidecar_transport; - if (!ddtrace_ffi_try("Failed connecting to master listener (thread mode)", - ddog_sidecar_connect_worker(ddtrace_sidecar_master_pid, &sidecar_transport))) { - LOG(WARN, "Failed to connect to master listener"); - return NULL; + // CLI without existing listener: start listener now (fallback for old behavior) +#endif + + ddtrace_set_non_resettable_sidecar_globals(); + ddtrace_set_resettable_sidecar_globals(); + + ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_activation, appsec_config); + + if (!ddtrace_ffi_try("Failed starting sidecar master listener", ddog_sidecar_connect_master((int32_t)ddtrace_sidecar_master_pid))) { + LOG(WARN, "Failed to start sidecar master listener"); + if (ddtrace_endpoint) { + dd_free_endpoints(); + } + return; + } + + LOG(INFO, "Started sidecar master listener thread (PID=%d)", ddtrace_sidecar_master_pid); + + ddog_SidecarTransport *sidecar_transport = NULL; + if (!ddtrace_ffi_try("Failed connecting master to sidecar", ddog_sidecar_connect_worker((int32_t)ddtrace_sidecar_master_pid, &sidecar_transport))) { + LOG(WARN, "Failed to connect master process to sidecar"); + if (ddtrace_endpoint) { + dd_free_endpoints(); + } + return; } char logpath[MAXPATHLEN]; @@ -239,15 +322,19 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { } dd_sidecar_post_connect(&sidecar_transport, false, logpath); + ddtrace_sidecar = sidecar_transport; + + ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_ex_non_fork); + + if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { + ddtrace_telemetry_first_init(); + } ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; +} - return sidecar_transport; -#else - // Thread mode not supported on Windows - LOG(ERROR, "Thread-based sidecar connection is not supported on Windows"); - return NULL; -#endif +ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { + return dd_sidecar_connection_factory_ex(false); } ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { @@ -338,28 +425,45 @@ bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_c } void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { + zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); + + if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD) { + ddtrace_sidecar_setup_master(appsec_activation, appsec_config); + return; + } + ddtrace_set_non_resettable_sidecar_globals(); ddtrace_set_resettable_sidecar_globals(); ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_activation, appsec_config); - ddtrace_sidecar = ddtrace_sidecar_connect(false); - if (!ddtrace_sidecar) { // Something went wrong + ddtrace_sidecar = ddtrace_sidecar_connect_subprocess(); + + if (!ddtrace_sidecar) { + if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO && ddtrace_endpoint) { + LOG(WARN, "Subprocess connection failed, falling back to thread mode"); + ddtrace_sidecar_setup_master(appsec_activation, appsec_config); + return; + } + if (ddtrace_endpoint) { dd_free_endpoints(); } - } - - if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { + } else if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { ddtrace_telemetry_first_init(); } } -// Initialize sidecar globals at module init void ddtrace_sidecar_minit(void) { #ifndef _WIN32 - if (ddtrace_sidecar_master_pid == 0) { - ddtrace_sidecar_master_pid = (int32_t)getpid(); + ddtrace_sidecar_master_pid = (int32_t)getpid(); + + zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); + + if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD || + mode == DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO) { + ddtrace_ffi_try("Starting sidecar master listener in MINIT", + ddog_sidecar_connect_master(ddtrace_sidecar_master_pid)); } #endif } diff --git a/tests/Common/WebFrameworkTestCase.php b/tests/Common/WebFrameworkTestCase.php index 022e6330d06..74e70ae60f6 100644 --- a/tests/Common/WebFrameworkTestCase.php +++ b/tests/Common/WebFrameworkTestCase.php @@ -26,7 +26,7 @@ abstract class WebFrameworkTestCase extends IntegrationTestCase /** * @var WebServer|null */ - private static $appServer; + protected static $appServer; protected $checkWebserverErrors = true; protected $cookiesFile; protected $maintainSession = false; diff --git a/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php b/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php new file mode 100644 index 00000000000..3cf39a6908a --- /dev/null +++ b/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php @@ -0,0 +1,91 @@ + 'sidecar-thread-mode-test', + // Explicitly force thread mode to test the thread implementation + 'DD_TRACE_SIDECAR_CONNECTION_MODE' => 'thread', + 'DD_TRACE_DEBUG' => '0', + ]); + } + + public function testThreadModeTracesAreSubmitted() + { + if (\getenv('DD_TRACE_TEST_SAPI') !== 'fpm-fcgi') { + $this->markTestSkipped('This test requires DD_TRACE_TEST_SAPI=fpm-fcgi'); + } + + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Thread mode not supported on Windows'); + } + + // This test validates that when thread mode is explicitly configured, + // traces are successfully submitted through the thread-based sidecar + $traces = $this->tracesFromWebRequest(function () { + $spec = GetSpec::create('Thread mode trace', '/simple?key=value'); + $this->call($spec); + }); + + // Verify traces were submitted + $this->assertNotEmpty($traces, 'Expected traces to be submitted through thread-based sidecar'); + $this->assertCount(1, $traces[0], 'Expected one span in the trace'); + + $span = $traces[0][0]; + $this->assertSame('web.request', $span['name']); + $this->assertSame('sidecar-thread-mode-test', $span['service']); + } + + public function testThreadModeMultipleRequests() + { + if (\getenv('DD_TRACE_TEST_SAPI') !== 'fpm-fcgi') { + $this->markTestSkipped('This test requires DD_TRACE_TEST_SAPI=fpm-fcgi'); + } + + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Thread mode not supported on Windows'); + } + + // This test validates that multiple PHP-FPM workers can successfully + // connect to the same master listener thread and submit traces + $traces = $this->tracesFromWebRequest(function () { + for ($i = 0; $i < 3; $i++) { + $spec = GetSpec::create("Request $i", "/simple?request=$i"); + $this->call($spec); + } + }); + + // Verify all traces were submitted + $this->assertGreaterThanOrEqual(3, count($traces), 'Expected at least 3 traces from multiple requests'); + + foreach ($traces as $trace) { + $this->assertNotEmpty($trace); + $this->assertSame('web.request', $trace[0]['name']); + } + } +} diff --git a/tests/Sapi/PhpFpm/PhpFpm.php b/tests/Sapi/PhpFpm/PhpFpm.php index 3c93541ddb4..bfe5035497f 100644 --- a/tests/Sapi/PhpFpm/PhpFpm.php +++ b/tests/Sapi/PhpFpm/PhpFpm.php @@ -34,23 +34,31 @@ final class PhpFpm implements Sapi */ private $logFile; + /** + * @var int + */ + private $maxChildren; + /** * @param string $rootPath * @param string $host * @param int $port * @param array $envs * @param array $inis + * @param int $maxChildren */ - public function __construct($rootPath, $host, $port, array $envs = [], array $inis = []) + public function __construct($rootPath, $host, $port, array $envs = [], array $inis = [], $maxChildren = 1) { $this->envs = $envs; $this->inis = $inis; + $this->maxChildren = $maxChildren; $logPath = $rootPath . '/' . self::ERROR_LOG; $replacements = [ '{{fcgi_host}}' => $host, '{{fcgi_port}}' => $port, + '{{max_children}}' => $maxChildren, '{{envs}}' => $this->envsForConfFile(), '{{inis}}' => $this->inisForConfFile(), '{{error_log}}' => $logPath, @@ -76,10 +84,17 @@ public function __construct($rootPath, $host, $port, array $envs = [], array $in public function start() { + $allowRoot = ''; + // Check if running as root and add --allow-to-run-as-root flag + if (function_exists('posix_getuid') && posix_getuid() === 0) { + $allowRoot = ' --allow-to-run-as-root'; + } + $cmd = sprintf( - 'php-fpm -p %s --fpm-config %s -F', + 'php-fpm -p %s --fpm-config %s -F%s', __DIR__, - $this->configFile + $this->configFile, + $allowRoot ); $processCmd = "exec $cmd"; diff --git a/tests/Sapi/PhpFpm/www.conf b/tests/Sapi/PhpFpm/www.conf index 6dd5da6df13..a8552244b9b 100644 --- a/tests/Sapi/PhpFpm/www.conf +++ b/tests/Sapi/PhpFpm/www.conf @@ -5,7 +5,7 @@ error_log = {{error_log}} listen = {{fcgi_host}}:{{fcgi_port}} pm = static -pm.max_children = 1 +pm.max_children = {{max_children}} pm.status_path = /status catch_workers_output = yes diff --git a/tests/WebServer.php b/tests/WebServer.php index 18c4679635c..c40be2da531 100644 --- a/tests/WebServer.php +++ b/tests/WebServer.php @@ -80,6 +80,7 @@ final class WebServer private $isOctane = false; private $isFrankenphp = false; private $isSwoole = false; + private $phpFpmMaxChildren = 1; private $defaultInis = [ 'log_errors' => 'on', @@ -132,6 +133,11 @@ public function setFrankenphp() $this->isFrankenphp = true; } + public function setPhpFpmMaxChildren($maxChildren) + { + $this->phpFpmMaxChildren = $maxChildren; + } + public function start() { if (!isset($this->envs['DD_TRACE_DEBUG'])) { @@ -192,7 +198,8 @@ public function start() self::FCGI_HOST, self::FCGI_PORT, $this->envs, - $this->inis + $this->inis, + $this->phpFpmMaxChildren ); break; case 'apache2handler': diff --git a/tests/ext/sidecar_connection_mode_auto.phpt b/tests/ext/sidecar_connection_mode_auto.phpt new file mode 100644 index 00000000000..449899f8d29 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_auto.phpt @@ -0,0 +1,24 @@ +--TEST-- +Sidecar connection in auto mode (default) +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=auto +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECT-- +Connection mode: auto +Span created successfully diff --git a/tests/ext/sidecar_connection_mode_config.phpt b/tests/ext/sidecar_connection_mode_config.phpt new file mode 100644 index 00000000000..b7ae94580fd --- /dev/null +++ b/tests/ext/sidecar_connection_mode_config.phpt @@ -0,0 +1,16 @@ +--TEST-- +DD_TRACE_SIDECAR_CONNECTION_MODE configuration parsing +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=subprocess +--FILE-- + +--EXPECT-- +Test 1: subprocess mode +string(10) "subprocess" diff --git a/tests/ext/sidecar_connection_mode_fork_warning.phpt b/tests/ext/sidecar_connection_mode_fork_warning.phpt new file mode 100644 index 00000000000..0a136b12cb8 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_fork_warning.phpt @@ -0,0 +1,38 @@ +--TEST-- +Fork with thread mode configuration (thread mode not active in CLI) +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=thread +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECTF-- +Connection mode: thread +Child process +Parent process diff --git a/tests/ext/sidecar_connection_mode_invalid.phpt b/tests/ext/sidecar_connection_mode_invalid.phpt new file mode 100644 index 00000000000..69db7138f94 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_invalid.phpt @@ -0,0 +1,24 @@ +--TEST-- +Invalid sidecar connection mode falls back to auto +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=invalid_mode +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECT-- +Connection mode: auto +Span created successfully diff --git a/tests/ext/sidecar_connection_mode_subprocess.phpt b/tests/ext/sidecar_connection_mode_subprocess.phpt new file mode 100644 index 00000000000..fb10c7a87c5 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_subprocess.phpt @@ -0,0 +1,24 @@ +--TEST-- +Sidecar connection in subprocess mode +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=subprocess +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECT-- +Connection mode: subprocess +Span created successfully diff --git a/tests/ext/sidecar_connection_mode_thread.phpt b/tests/ext/sidecar_connection_mode_thread.phpt new file mode 100644 index 00000000000..eb76ef9f175 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_thread.phpt @@ -0,0 +1,31 @@ +--TEST-- +Sidecar connection in thread mode +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=thread +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECTF-- +Connection mode: thread +Test completed diff --git a/tests/ext/sidecar_connection_mode_thread_functional.phpt b/tests/ext/sidecar_connection_mode_thread_functional.phpt new file mode 100644 index 00000000000..bbd7563e8be --- /dev/null +++ b/tests/ext/sidecar_connection_mode_thread_functional.phpt @@ -0,0 +1,98 @@ +--TEST-- +Thread mode sidecar connection sends traces (CLI with fallback to subprocess) +--SKIPIF-- + + + +--ENV-- +DD_TRACE_LOG_LEVEL=info,startup=off +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +DD_TRACE_SIDECAR_CONNECTION_MODE=auto +--INI-- +datadog.trace.agent_test_session_token=sidecar_thread_functional_test +--FILE-- +replayRequest(); + +echo "Connection mode: " . ini_get('datadog.trace.sidecar_connection_mode') . "\n"; + +// Create a span that should be sent via sidecar +DDTrace\start_span(); +DDTrace\active_span()->name = 'thread.mode.test'; +DDTrace\active_span()->service = 'thread-mode-functional-test'; +DDTrace\active_span()->resource = 'test_resource'; +DDTrace\active_span()->meta['test.key'] = 'test.value'; +DDTrace\close_span(); + +echo "Span created\n"; + +// Wait for trace to be sent and retrieve it +$trace_data = $rr->waitForDataAndReplay(); + +echo "Trace received\n"; + +// Parse the trace +$decoded = json_decode($trace_data["body"], true); + +// Handle both chunked and non-chunked formats +$spans = $decoded["chunks"][0]["spans"] ?? $decoded[0]; + +if (!is_array($spans) || empty($spans)) { + die("FAIL: No spans in trace\n"); +} + +$root_span = $spans[0]; + +// Verify span properties +if ($root_span["name"] !== "thread.mode.test") { + die("FAIL: Expected span name 'thread.mode.test', got: " . $root_span["name"] . "\n"); +} + +if ($root_span["service"] !== "thread-mode-functional-test") { + die("FAIL: Expected service 'thread-mode-functional-test', got: " . $root_span["service"] . "\n"); +} + +if ($root_span["resource"] !== "test_resource") { + die("FAIL: Expected resource 'test_resource', got: " . $root_span["resource"] . "\n"); +} + +if (!isset($root_span["meta"]["test.key"]) || $root_span["meta"]["test.key"] !== "test.value") { + die("FAIL: Expected meta tag 'test.key' = 'test.value'\n"); +} + +echo "Span properties verified\n"; +echo "Test passed: Sidecar connection works and traces are sent\n"; + +?> +--EXPECTF-- +Connection mode: auto +%a +Span created +Trace received +Span properties verified +Test passed: Sidecar connection works and traces are sent +%a diff --git a/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt b/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt new file mode 100644 index 00000000000..99c13eb961f --- /dev/null +++ b/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt @@ -0,0 +1,243 @@ +--TEST-- +Thread mode connection with PHP-FPM (manual verification test) +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=thread +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +DD_TRACE_DEBUG=1 +--FILE-- +name = 'phpfpm.request'; +DDTrace\active_span()->service = 'thread-mode-test'; +DDTrace\active_span()->resource = 'GET /test'; +DDTrace\close_span(); + +echo "Request processed with thread mode\n"; +echo "Connection mode: " . ini_get('datadog.trace.sidecar_connection_mode') . "\n"; +PHP +); + +// Create PHP-FPM configuration +$fpmConfig = $tmpDir . '/php-fpm.conf'; +$fpmSocket = $tmpDir . '/php-fpm.sock'; +$fpmLog = $tmpDir . '/php-fpm.log'; + +file_put_contents($fpmConfig, << ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], +]; + +$fpmCmd = "php-fpm --fpm-config $fpmConfig --nodaemonize"; +$fpmProc = proc_open($fpmCmd, $descriptors, $pipes); + +if (!is_resource($fpmProc)) { + die("FAIL: Could not start PHP-FPM\n"); +} + +// Give PHP-FPM time to start and initialize thread mode +echo "Waiting for PHP-FPM to start...\n"; +$timeout = 5; +$start = time(); +while (!file_exists($fpmSocket) && (time() - $start) < $timeout) { + usleep(100000); // 100ms +} + +if (!file_exists($fpmSocket)) { + proc_terminate($fpmProc); + die("FAIL: PHP-FPM socket not created within timeout\n"); +} + +echo "PHP-FPM started successfully!\n\n"; + +// Make a FastCGI request to PHP-FPM +echo "Making FastCGI request...\n"; + +// Simple FastCGI client implementation for testing +$sock = stream_socket_client("unix://$fpmSocket", $errno, $errstr, 5); +if (!$sock) { + proc_terminate($fpmProc); + die("FAIL: Could not connect to PHP-FPM socket: $errstr\n"); +} + +// Build FastCGI request +$params = [ + 'GATEWAY_INTERFACE' => 'FastCGI/1.0', + 'REQUEST_METHOD' => 'GET', + 'SCRIPT_FILENAME' => $scriptPath, + 'SCRIPT_NAME' => '/index.php', + 'REQUEST_URI' => '/test', + 'DOCUMENT_ROOT' => $tmpDir, + 'SERVER_SOFTWARE' => 'php/fcgi', + 'REMOTE_ADDR' => '127.0.0.1', + 'REMOTE_PORT' => '9000', + 'SERVER_ADDR' => '127.0.0.1', + 'SERVER_PORT' => '80', + 'SERVER_NAME' => 'localhost', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'CONTENT_TYPE' => '', + 'CONTENT_LENGTH' => '0', +]; + +// Simplified FastCGI protocol implementation +function fcgi_begin_request($sock, $requestId, $role = 1, $flags = 0) { + $content = pack('nCx5', $role, $flags); + fcgi_write_record($sock, 1, $requestId, $content); // Type 1 = BEGIN_REQUEST +} + +function fcgi_write_params($sock, $requestId, $params) { + $content = ''; + foreach ($params as $key => $value) { + $keyLen = strlen($key); + $valLen = strlen($value); + + $content .= chr($keyLen); + $content .= chr($valLen); + $content .= $key . $value; + } + fcgi_write_record($sock, 4, $requestId, $content); // Type 4 = PARAMS + fcgi_write_record($sock, 4, $requestId, ''); // Empty PARAMS to signal end +} + +function fcgi_write_record($sock, $type, $requestId, $content) { + $clen = strlen($content); + $header = pack('CCnnxx', 1, $type, $requestId, $clen); + fwrite($sock, $header . $content); +} + +// Send FastCGI request +$requestId = 1; +fcgi_begin_request($sock, $requestId); +fcgi_write_params($sock, $requestId, $params); +fcgi_write_record($sock, 5, $requestId, ''); // Type 5 = STDIN (empty) + +// Read response +$response = ''; +$timeout = 5; +$start = time(); +stream_set_timeout($sock, 1); + +while (!feof($sock) && (time() - $start) < $timeout) { + $chunk = fread($sock, 8192); + if ($chunk === false) break; + $response .= $chunk; + if (strpos($response, "Request processed with thread mode") !== false) { + break; + } +} + +fclose($sock); + +// Parse response (simplified - just look for our output) +if (strpos($response, "Request processed with thread mode") !== false) { + echo "SUCCESS: Request processed through thread mode!\n"; + if (strpos($response, "Connection mode: thread") !== false) { + echo "SUCCESS: Thread mode configuration verified!\n"; + } +} else { + echo "FAIL: Did not receive expected response\n"; + echo "Response snippet: " . substr($response, 0, 200) . "\n"; +} + +echo "\n=== Test Complete ===\n"; +echo "Note: This test verifies that:\n"; +echo "1. PHP-FPM master process can start with thread mode\n"; +echo "2. PHP-FPM workers can process requests\n"; +echo "3. Thread mode configuration is active\n"; +echo "\nFor full verification of trace submission, check DD_TRACE_DEBUG logs\n"; +echo "showing master listener thread startup and worker connections.\n"; + +// Cleanup +echo "\nCleaning up...\n"; +proc_terminate($fpmProc); +proc_close($fpmProc); +unlink($scriptPath); +unlink($fpmConfig); +unlink($fpmSocket); +rmdir($tmpDir); + +?> +--EXPECTF-- +=== PHP-FPM Thread Mode Test === + +Test directory: %s +Socket: %s + +Starting PHP-FPM with thread mode... +Waiting for PHP-FPM to start... +PHP-FPM started successfully! + +Making FastCGI request... +SUCCESS: Request processed through thread mode! +SUCCESS: Thread mode configuration verified! + +=== Test Complete === +Note: This test verifies that: +1. PHP-FPM master process can start with thread mode +2. PHP-FPM workers can process requests +3. Thread mode configuration is active + +For full verification of trace submission, check DD_TRACE_DEBUG logs +showing master listener thread startup and worker connections. + +Cleaning up...