From 44f78fd7759057d35aeacc2f55fe6ca7ac4ccb82 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 11:34:58 +0100 Subject: [PATCH 01/10] 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 8984674f682..c4d8b116996 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 f5dbd2a5046..8f64ec9c1e5 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -560,6 +560,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 f3ca6d9881a169d7c3e781c8f710d80dcdc457aa Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 11:35:17 +0100 Subject: [PATCH 02/10] feat(tracer): add sidecar thread listener module Signed-off-by: Alexandre Rulleau --- appsec/third_party/libddwaf | 2 +- components-rs/ddtrace.h | 10 ++++++++++ components-rs/sidecar.h | 38 +++++++++++++++++++++++++++++++++++++ libdatadog | 2 +- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/appsec/third_party/libddwaf b/appsec/third_party/libddwaf index 2f4aa84cd61..00e895f2c50 160000 --- a/appsec/third_party/libddwaf +++ b/appsec/third_party/libddwaf @@ -1 +1 @@ -Subproject commit 2f4aa84cd61dc13229d1431779c007bf4ebda89c +Subproject commit 00e895f2c507a714062aa88ed41466aec10d2e01 diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index c0bab2a9eab..081da376331 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -120,6 +120,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 2d694f91507..8577ee8fe19 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 a0cef26b024..cc2fb3d1ae4 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit a0cef26b0240f19dd994d471d5679e8c426adfc8 +Subproject commit cc2fb3d1ae47cc0cabee323cb96202a318866bbe From c3cd9ad9e092df979ce65ea7b78d759ac05b895e Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 12:10:00 +0100 Subject: [PATCH 03/10] 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 e636e104684..ecc542b5452 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1530,6 +1530,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(); @@ -1613,7 +1614,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(); ddtrace_process_tags_mshutdown(); @@ -2660,6 +2665,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 73b653fd7cb..10eb542bd8f 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -27,6 +27,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())) { @@ -188,6 +192,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) { @@ -219,7 +365,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) { @@ -252,7 +411,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(); @@ -264,6 +424,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); @@ -291,8 +460,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) { @@ -301,7 +491,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) { @@ -316,6 +510,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) { @@ -626,6 +833,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 5fa16bd2793..ecdd678ef0c 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 62bddd78baee831d9335d93c3f6836bf0f59983f Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 13 Jan 2026 14:11:49 +0100 Subject: [PATCH 04/10] 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 ecc542b5452..e4955fc2536 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1587,7 +1587,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 10eb542bd8f..5906d227336 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" @@ -192,7 +194,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; @@ -223,13 +224,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; @@ -241,12 +240,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))) { @@ -258,7 +255,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))) { @@ -274,7 +270,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; @@ -285,15 +280,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)"); @@ -302,7 +307,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)"); @@ -311,14 +316,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(); @@ -334,51 +342,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) { @@ -411,8 +376,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(); @@ -435,7 +399,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); } } @@ -512,19 +476,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(); } @@ -835,7 +788,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 ecdd678ef0c..6fbdd19e85d 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 746e05fd75bca354b226a59612da6b1f068b4212 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Fri, 16 Jan 2026 13:06:18 +0100 Subject: [PATCH 05/10] fix(tracer): remove debug logs Signed-off-by: Alexandre Rulleau --- components-rs/common.h | 247 ++++++++++----------- components-rs/ddtrace.h | 10 - components-rs/sidecar.h | 65 ++++-- 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 - 7 files changed, 177 insertions(+), 180 deletions(-) diff --git a/components-rs/common.h b/components-rs/common.h index e53c4ef311d..80d130b081e 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -264,16 +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_CONFIGURATION_ORIGIN_CALCULATED, -} 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, @@ -282,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, @@ -314,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, @@ -328,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, @@ -390,23 +406,6 @@ typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_FFE_FLAG_CONFIGURATION_RULES = 46, } 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_FFE_FLAGS, - 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; @@ -777,18 +776,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_SNAPSHOTS, - 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; @@ -917,16 +916,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; @@ -945,6 +934,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; /** @@ -1096,37 +1095,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. @@ -1151,6 +1141,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 @@ -1223,28 +1219,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; diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index 081da376331..c0bab2a9eab 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -120,16 +120,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 8577ee8fe19..bba365b4965 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); @@ -159,17 +187,6 @@ ddog_MaybeError ddog_sidecar_telemetry_enqueueConfig(struct ddog_SidecarTranspor ddog_CharSlice config_id, struct ddog_Option_U64 seq_id); -/** - * Reports an endpoint to the telemetry. - */ -ddog_MaybeError ddog_sidecar_telemetry_addEndpoint(struct ddog_SidecarTransport **transport, - const struct ddog_InstanceId *instance_id, - const ddog_QueueId *queue_id, - enum ddog_Method method, - ddog_CharSlice path, - ddog_CharSlice operation_name, - ddog_CharSlice resource_name); - /** * Reports a dependency to the telemetry. */ diff --git a/ext/sidecar.c b/ext/sidecar.c index 5906d227336..27bc8506855 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -298,7 +298,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)"); @@ -307,7 +306,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)"); @@ -317,23 +315,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 1b893ff432b39a5d73128043e66e785d76ef8531 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Mon, 19 Jan 2026 17:24:45 +0100 Subject: [PATCH 06/10] 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 27bc8506855..09c6aa33ab8 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -139,6 +139,10 @@ void ddtrace_sidecar_update_process_tags(void) { ddog_sidecar_session_set_process_tags(&ddtrace_sidecar, session_id, dd_zend_string_to_CharSlice(process_tags)); } +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; @@ -229,37 +233,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]; @@ -269,15 +352,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) { @@ -368,28 +455,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 239e465af8b..2da750cfabc 100644 --- a/tests/Sapi/PhpFpm/PhpFpm.php +++ b/tests/Sapi/PhpFpm/PhpFpm.php @@ -44,25 +44,33 @@ final class PhpFpm implements Sapi */ private $port; + /** + * @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->host = $host; $this->port = $port; + $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, @@ -88,10 +96,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 ce7d27c1dbf..e50639f74bd 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... From cad9653587ad251ef38fed7cadc1a602819a65b5 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Tue, 20 Jan 2026 14:10:13 +0100 Subject: [PATCH 07/10] feat(tracer): fix fork with thread mode Signed-off-by: Alexandre Rulleau --- ext/ddtrace.c | 20 ++++-- ext/handlers_pcntl.c | 8 --- ext/sidecar.c | 68 ++++++++++++++++--- ext/sidecar.h | 1 + tests/ext/sidecar_connection_mode_thread.phpt | 31 --------- 5 files changed, 76 insertions(+), 52 deletions(-) delete mode 100644 tests/ext/sidecar_connection_mode_thread.phpt diff --git a/ext/ddtrace.c b/ext/ddtrace.c index e4955fc2536..a5d80f484d2 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -2678,9 +2678,17 @@ void dd_internal_handle_fork(void) { 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"); + // Attempt to reconnect child to parent's master listener + bool appsec_activation = false; + bool appsec_config = false; + bool enable_sidecar = ddtrace_sidecar_maybe_enable_appsec(&appsec_activation, &appsec_config); + if (!enable_sidecar) { + enable_sidecar = get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER(); + } + + if (enable_sidecar && !ddtrace_sidecar_reconnect_after_fork(appsec_activation, appsec_config)) { + LOG(WARN, "Child process after fork with thread mode: failed to reconnect to parent's listener"); + } } #endif if (DDTRACE_G(agent_config_reader)) { @@ -2697,7 +2705,11 @@ void dd_internal_handle_fork(void) { } ddtrace_seed_prng(); ddtrace_generate_runtime_id(); - ddtrace_reset_sidecar(); + // Thread mode already handled sidecar reconnection above (lines 2648-2664) + // Only reset for subprocess mode + if (ddtrace_sidecar_active_mode != DD_SIDECAR_CONNECTION_THREAD) { + ddtrace_reset_sidecar(); + } if (!get_DD_TRACE_FORKED_PROCESS()) { ddtrace_disable_tracing_in_current_request(); } diff --git a/ext/handlers_pcntl.c b/ext/handlers_pcntl.c index 22b5b31d32a..ae908d84221 100644 --- a/ext/handlers_pcntl.c +++ b/ext/handlers_pcntl.c @@ -38,14 +38,6 @@ 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 09c6aa33ab8..cbce5dc7a39 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -140,7 +140,7 @@ void ddtrace_sidecar_update_process_tags(void) { } static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork); -static ddog_SidecarTransport *dd_sidecar_connection_factory_ex_non_fork(void); +static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void); static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_config); static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { @@ -261,14 +261,15 @@ static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { dd_sidecar_post_connect(&sidecar_transport, is_fork, logpath); - ddtrace_sidecar_reconnect(&sidecar_transport, dd_sidecar_connection_factory_ex_non_fork); + ddtrace_sidecar_reconnect(&sidecar_transport, dd_sidecar_connection_factory_thread); ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; return sidecar_transport; } -static ddog_SidecarTransport *dd_sidecar_connection_factory_ex_non_fork(void) { +// Connection factory for thread mode - connects as worker to master listener +static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void) { return dd_sidecar_connection_factory_ex(false); } @@ -307,7 +308,7 @@ static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_con dd_sidecar_post_connect(&sidecar_transport, false, logpath); ddtrace_sidecar = sidecar_transport; - ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_ex_non_fork); + ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_thread); ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; @@ -354,7 +355,7 @@ static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_con dd_sidecar_post_connect(&sidecar_transport, false, logpath); ddtrace_sidecar = sidecar_transport; - ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_ex_non_fork); + ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_thread); if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { ddtrace_telemetry_first_init(); @@ -368,10 +369,8 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_thread(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; - } + // Thread mode fork is handled by ddtrace_sidecar_reconnect_after_fork() in ddtrace.c + // This function is only used for subprocess mode or initial connections if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { return ddtrace_sidecar_connect_subprocess(); @@ -498,6 +497,57 @@ void ddtrace_sidecar_minit(void) { #endif } +// Reconnect to parent's master listener after fork in thread mode +bool ddtrace_sidecar_reconnect_after_fork(bool appsec_activation, bool appsec_config) { +#ifndef _WIN32 + // Check if there's a master listener available to connect to + if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { + return false; + } + + // Set up endpoints and configuration + 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_endpoint) { + LOG(WARN, "Cannot reconnect to sidecar after fork: endpoint not available"); + return false; + } + + // Attempt to connect as a worker to parent's listener + ddog_SidecarTransport *sidecar_transport = NULL; + if (!ddtrace_ffi_try("Failed connecting child to parent's sidecar listener after fork", + ddog_sidecar_connect_worker(ddtrace_sidecar_master_pid, &sidecar_transport))) { + LOG(WARN, "Failed to connect child to parent's sidecar listener (child PID=%d, parent=%d)", + (int32_t)getpid(), ddtrace_sidecar_master_pid); + return false; + } + + // Set up the connection + 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, true, logpath); // is_fork=true: this IS a fork scenario + ddtrace_sidecar = sidecar_transport; + + ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_thread); + + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; + + LOG(INFO, "Child process reconnected to parent's sidecar listener after fork (child PID=%d, parent=%d)", + (int32_t)getpid(), ddtrace_sidecar_master_pid); + + return true; +#else + return false; +#endif +} + void ddtrace_sidecar_ensure_active(void) { if (ddtrace_sidecar) { ddtrace_sidecar_reconnect(&ddtrace_sidecar, ddtrace_sidecar_connect_callback); diff --git a/ext/sidecar.h b/ext/sidecar.h index 6fbdd19e85d..7fbf10507c4 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -37,6 +37,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork); // Lifecycle functions void ddtrace_sidecar_minit(void); void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config); +bool ddtrace_sidecar_reconnect_after_fork(bool appsec_activation, bool appsec_config); bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_config); void ddtrace_sidecar_ensure_active(void); void ddtrace_sidecar_update_process_tags(void); diff --git a/tests/ext/sidecar_connection_mode_thread.phpt b/tests/ext/sidecar_connection_mode_thread.phpt deleted file mode 100644 index eb76ef9f175..00000000000 --- a/tests/ext/sidecar_connection_mode_thread.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---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 From 40ed7d040a1d86fde74caf9d83b294e4c2d6f9e1 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Thu, 22 Jan 2026 13:45:44 +0100 Subject: [PATCH 08/10] apply feedbacks Signed-off-by: Alexandre Rulleau --- components-rs/sidecar.h | 56 ---- ext/ddtrace.c | 45 +--- ext/handlers_pcntl.c | 1 - ext/sidecar.c | 222 ++++++---------- ext/sidecar.h | 6 +- tests/Common/WebFrameworkTestCase.php | 2 +- .../Custom/Autoloaded/InstrumentationTest.php | 1 - .../sidecar_connection_mode_fork_warning.phpt | 38 --- ...car_connection_mode_thread_functional.phpt | 98 ------- ...sidecar_connection_mode_thread_phpfpm.phpt | 243 ------------------ 10 files changed, 91 insertions(+), 621 deletions(-) delete mode 100644 tests/ext/sidecar_connection_mode_fork_warning.phpt delete mode 100644 tests/ext/sidecar_connection_mode_thread_functional.phpt delete mode 100644 tests/ext/sidecar_connection_mode_thread_phpfpm.phpt diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index bba365b4965..551298038e6 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -92,70 +92,14 @@ 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); diff --git a/ext/ddtrace.c b/ext/ddtrace.c index a5d80f484d2..525ccd275f7 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -454,10 +454,7 @@ static void dd_activate_once(void) { } // if we're to enable appsec, we need to enable sidecar - bool enable_sidecar = ddtrace_sidecar_maybe_enable_appsec(&appsec_activation, &appsec_config); - if (!enable_sidecar) { - enable_sidecar = get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER(); - } + bool enable_sidecar = ddtrace_sidecar_should_enable(&appsec_activation, &appsec_config); if (enable_sidecar) #endif @@ -1587,9 +1584,7 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) { #ifndef _WIN32 ddtrace_signals_mshutdown(); - // 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) { + if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) { ddtrace_coms_mshutdown(); if (ddtrace_coms_flush_shutdown_writer_synchronous()) { ddtrace_coms_curl_shutdown(); @@ -1616,11 +1611,7 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) { ddtrace_user_req_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_sidecar_shutdown(); ddtrace_live_debugger_mshutdown(); ddtrace_process_tags_mshutdown(); @@ -2667,30 +2658,9 @@ 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()); - - // Attempt to reconnect child to parent's master listener - bool appsec_activation = false; - bool appsec_config = false; - bool enable_sidecar = ddtrace_sidecar_maybe_enable_appsec(&appsec_activation, &appsec_config); - if (!enable_sidecar) { - enable_sidecar = get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER(); - } - - if (enable_sidecar && !ddtrace_sidecar_reconnect_after_fork(appsec_activation, appsec_config)) { - LOG(WARN, "Child process after fork with thread mode: failed to reconnect to parent's listener"); - } - } #endif + + ddtrace_sidecar_handle_fork(); if (DDTRACE_G(agent_config_reader)) { ddog_agent_remote_config_reader_drop(DDTRACE_G(agent_config_reader)); DDTRACE_G(agent_config_reader) = NULL; @@ -2705,11 +2675,6 @@ void dd_internal_handle_fork(void) { } ddtrace_seed_prng(); ddtrace_generate_runtime_id(); - // Thread mode already handled sidecar reconnection above (lines 2648-2664) - // Only reset for subprocess mode - if (ddtrace_sidecar_active_mode != DD_SIDECAR_CONNECTION_THREAD) { - ddtrace_reset_sidecar(); - } if (!get_DD_TRACE_FORKED_PROCESS()) { ddtrace_disable_tracing_in_current_request(); } diff --git a/ext/handlers_pcntl.c b/ext/handlers_pcntl.c index ae908d84221..acff140cfe4 100644 --- a/ext/handlers_pcntl.c +++ b/ext/handlers_pcntl.c @@ -37,7 +37,6 @@ static void dd_prefork() { static void dd_handle_fork(zval *return_value) { if (Z_LVAL_P(return_value) == 0) { - // CHILD PROCESS dd_internal_handle_fork(); } else { #if JOIN_BGS_BEFORE_FORK diff --git a/ext/sidecar.c b/ext/sidecar.c index cbce5dc7a39..ba7941b32e5 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -140,8 +140,10 @@ void ddtrace_sidecar_update_process_tags(void) { } static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork); +#ifndef _WIN32 static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void); -static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_config); +static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appsec_config); +#endif static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { if (!ddtrace_endpoint || !dogstatsd_endpoint) { @@ -233,8 +235,8 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void) { return sidecar_transport; } -static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { - // Should not happen, unless the agent url is malformed +#ifndef _WIN32 +static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { if (!ddtrace_endpoint) { return NULL; } @@ -242,10 +244,6 @@ static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { 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) { @@ -270,10 +268,11 @@ static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { // Connection factory for thread mode - connects as worker to master listener static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void) { - return dd_sidecar_connection_factory_ex(false); + return ddtrace_sidecar_connect_as_worker(false); } +#endif -static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_config) { +static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appsec_config) { #ifndef _WIN32 pid_t current_pid = getpid(); bool is_child_process = (ddtrace_sidecar_master_pid != 0 && (int32_t)current_pid != ddtrace_sidecar_master_pid); @@ -286,40 +285,19 @@ static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_con ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_activation, appsec_config); - if (!ddtrace_endpoint) { - LOG(WARN, "Cannot connect to sidecar: endpoint not available"); - return; - } - - 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))) { + ddtrace_sidecar = ddtrace_sidecar_connect_as_worker(false); + if (!ddtrace_sidecar) { 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; - - ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_thread); - - 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; } - - // CLI without existing listener: start listener now (fallback for old behavior) #endif ddtrace_set_non_resettable_sidecar_globals(); @@ -337,46 +315,32 @@ static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_con 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))) { + ddtrace_sidecar = ddtrace_sidecar_connect_as_worker(false); + if (!ddtrace_sidecar) { LOG(WARN, "Failed to connect master process to sidecar"); - if (ddtrace_endpoint) { - dd_free_endpoints(); - } 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; - - ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_thread); - if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { ddtrace_telemetry_first_init(); } - - ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; } +#ifndef _WIN32 ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { - return dd_sidecar_connection_factory_ex(false); + return ddtrace_sidecar_connect_as_worker(false); } +#endif ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { - // Thread mode fork is handled by ddtrace_sidecar_reconnect_after_fork() in ddtrace.c - // This function is only used for subprocess mode or initial connections - if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { return ddtrace_sidecar_connect_subprocess(); - } else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + } +#ifndef _WIN32 + else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { return ddtrace_sidecar_connect_thread(); } +#endif zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); ddog_SidecarTransport *transport = NULL; @@ -390,6 +354,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { } break; +#ifndef _WIN32 case DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD: // Force thread only transport = ddtrace_sidecar_connect_thread(); @@ -397,6 +362,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { LOG(ERROR, "Thread connection failed (mode=thread, no fallback)"); } break; +#endif case DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO: default: @@ -404,10 +370,8 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { transport = ddtrace_sidecar_connect_subprocess(); 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 { + if (ddtrace_endpoint) { +#ifndef _WIN32 // 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(); @@ -417,6 +381,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { } else { LOG(ERROR, "Both subprocess and thread connections failed, sidecar unavailable"); } +#endif } } break; @@ -453,13 +418,24 @@ bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_c #endif } +bool ddtrace_sidecar_should_enable(bool *appsec_activation, bool *appsec_config) { + bool enable_sidecar = ddtrace_sidecar_maybe_enable_appsec(appsec_activation, appsec_config); + if (!enable_sidecar) { + enable_sidecar = get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || + get_global_DD_TRACE_SIDECAR_TRACE_SENDER(); + } + return enable_sidecar; +} + void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); +#ifndef _WIN32 if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD) { - ddtrace_sidecar_setup_master(appsec_activation, appsec_config); + ddtrace_sidecar_setup_thread_mode(appsec_activation, appsec_config); return; } +#endif ddtrace_set_non_resettable_sidecar_globals(); ddtrace_set_resettable_sidecar_globals(); @@ -469,11 +445,13 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { ddtrace_sidecar = ddtrace_sidecar_connect_subprocess(); if (!ddtrace_sidecar) { +#ifndef _WIN32 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); + ddtrace_sidecar_setup_thread_mode(appsec_activation, appsec_config); return; } +#endif if (ddtrace_endpoint) { dd_free_endpoints(); @@ -489,62 +467,64 @@ void ddtrace_sidecar_minit(void) { 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) { + if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD) { ddtrace_ffi_try("Starting sidecar master listener in MINIT", ddog_sidecar_connect_master(ddtrace_sidecar_master_pid)); } #endif } -// Reconnect to parent's master listener after fork in thread mode -bool ddtrace_sidecar_reconnect_after_fork(bool appsec_activation, bool appsec_config) { +void ddtrace_sidecar_handle_fork(void) { #ifndef _WIN32 - // Check if there's a master listener available to connect to - if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { - return false; - } + bool appsec_activation = false; + bool appsec_config = false; + bool enable_sidecar = ddtrace_sidecar_should_enable(&appsec_activation, &appsec_config); - // Set up endpoints and configuration - 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_endpoint) { - LOG(WARN, "Cannot reconnect to sidecar after fork: endpoint not available"); - return false; + if (!enable_sidecar) { + return; } - // Attempt to connect as a worker to parent's listener - ddog_SidecarTransport *sidecar_transport = NULL; - if (!ddtrace_ffi_try("Failed connecting child to parent's sidecar listener after fork", - ddog_sidecar_connect_worker(ddtrace_sidecar_master_pid, &sidecar_transport))) { - LOG(WARN, "Failed to connect child to parent's sidecar listener (child PID=%d, parent=%d)", - (int32_t)getpid(), ddtrace_sidecar_master_pid); - return false; - } + // Handle thread mode fork - reconnect to parent's listener + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + ddtrace_ffi_try("Failed clearing inherited listener state", + ddog_sidecar_clear_inherited_listener()); - // Set up the connection - 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; - } + if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { + LOG(WARN, "Child process cannot reconnect: parent's listener not active"); + return; + } - dd_sidecar_post_connect(&sidecar_transport, true, logpath); // is_fork=true: this IS a fork scenario - ddtrace_sidecar = sidecar_transport; + ddtrace_force_new_instance_id(); - ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory_thread); + // Attempt to connect as a worker to parent's listener + ddtrace_sidecar = ddtrace_sidecar_connect_as_worker(true); + if (!ddtrace_sidecar) { + LOG(WARN, "Failed to connect child to parent's sidecar listener (child PID=%d, parent=%d)", + (int32_t)getpid(), ddtrace_sidecar_master_pid); + return; + } - ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; + LOG(INFO, "Child process reconnected to parent's sidecar listener after fork (child PID=%d, parent=%d)", + (int32_t)getpid(), ddtrace_sidecar_master_pid); + } + // Handle subprocess mode fork - reset and spawn new subprocess + else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { + ddtrace_force_new_instance_id(); - LOG(INFO, "Child process reconnected to parent's sidecar listener after fork (child PID=%d, parent=%d)", - (int32_t)getpid(), ddtrace_sidecar_master_pid); + if (ddtrace_sidecar) { + ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; - return true; -#else - return false; + ddtrace_sidecar = ddtrace_sidecar_connect(true); + if (!ddtrace_sidecar) { + if (ddtrace_endpoint) { + dd_free_endpoints(); + } + } else { + ddtrace_sidecar_submit_root_span_data(); + } + } + } #endif } @@ -620,24 +600,6 @@ void ddtrace_force_new_instance_id(void) { } } -void ddtrace_reset_sidecar(void) { - ddtrace_force_new_instance_id(); - - if (ddtrace_sidecar) { - ddog_sidecar_transport_drop(ddtrace_sidecar); - ddtrace_sidecar = NULL; - - ddtrace_sidecar = ddtrace_sidecar_connect(true); - if (!ddtrace_sidecar) { - if (ddtrace_endpoint) { - dd_free_endpoints(); - } - } else { - ddtrace_sidecar_submit_root_span_data(); - } - } -} - ddog_Endpoint *ddtrace_sidecar_agent_endpoint(void) { ddog_Endpoint *agent_endpoint; @@ -937,28 +899,6 @@ 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) - // 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(); - } } 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 7fbf10507c4..a337187d409 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -31,19 +31,21 @@ DDTRACE_PUBLIC struct telemetry_rc_info ddtrace_get_telemetry_rc_info(void); // Connection functions ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void); +#ifndef _WIN32 ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void); +#endif ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork); // Lifecycle functions void ddtrace_sidecar_minit(void); void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config); -bool ddtrace_sidecar_reconnect_after_fork(bool appsec_activation, bool appsec_config); +void ddtrace_sidecar_handle_fork(void); bool ddtrace_sidecar_maybe_enable_appsec(bool *appsec_activation, bool *appsec_config); +bool ddtrace_sidecar_should_enable(bool *appsec_activation, bool *appsec_config); void ddtrace_sidecar_ensure_active(void); void ddtrace_sidecar_update_process_tags(void); void ddtrace_sidecar_finalize(bool clear_id); void ddtrace_sidecar_shutdown(void); -void ddtrace_reset_sidecar(void); void ddtrace_force_new_instance_id(void); void ddtrace_sidecar_submit_root_span_data(void); void ddtrace_sidecar_push_tag(ddog_Vec_Tag *vec, ddog_CharSlice key, ddog_CharSlice value); diff --git a/tests/Common/WebFrameworkTestCase.php b/tests/Common/WebFrameworkTestCase.php index 74e70ae60f6..022e6330d06 100644 --- a/tests/Common/WebFrameworkTestCase.php +++ b/tests/Common/WebFrameworkTestCase.php @@ -26,7 +26,7 @@ abstract class WebFrameworkTestCase extends IntegrationTestCase /** * @var WebServer|null */ - protected static $appServer; + private static $appServer; protected $checkWebserverErrors = true; protected $cookiesFile; protected $maintainSession = false; diff --git a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php index d42cfcc4e50..eb85ee4a56e 100644 --- a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php +++ b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php @@ -23,7 +23,6 @@ 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/sidecar_connection_mode_fork_warning.phpt b/tests/ext/sidecar_connection_mode_fork_warning.phpt deleted file mode 100644 index 0a136b12cb8..00000000000 --- a/tests/ext/sidecar_connection_mode_fork_warning.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---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_thread_functional.phpt b/tests/ext/sidecar_connection_mode_thread_functional.phpt deleted file mode 100644 index bbd7563e8be..00000000000 --- a/tests/ext/sidecar_connection_mode_thread_functional.phpt +++ /dev/null @@ -1,98 +0,0 @@ ---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 deleted file mode 100644 index 99c13eb961f..00000000000 --- a/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt +++ /dev/null @@ -1,243 +0,0 @@ ---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... From 401bde228be3a82af73cc059538943ad51df2ec8 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Fri, 23 Jan 2026 13:28:43 +0100 Subject: [PATCH 09/10] feat(sidecar): support threaded connection for windows Signed-off-by: Alexandre Rulleau --- Cargo.lock | 16 +++++++ components-rs/bytes.rs | 46 +++++++++---------- components-rs/common.h | 13 ++++++ components-rs/sidecar.h | 11 +++++ ext/coms.c | 4 -- ext/sidecar.c | 39 ++++++---------- ext/sidecar.h | 2 - .../Autoloaded/SidecarThreadModeTest.php | 8 ---- 8 files changed, 78 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 588b1468b4b..eb1bc4922df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2814,6 +2814,7 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD name = "libdd-common" version = "1.1.0" source = "git+https://github.com/DataDog/libdatadog?tag=v27.0.0#72e56a3dcf9189a92db1f177c4c9d844725079f7" @@ -2852,6 +2853,8 @@ dependencies = [ ] [[package]] +======= +>>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) name = "libdd-common-ffi" version = "0.0.1" dependencies = [ @@ -2862,7 +2865,11 @@ dependencies = [ "chrono", "crossbeam-queue", "function_name", +<<<<<<< HEAD "hyper 1.6.0", +======= + "hyper", +>>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) "libdd-common 1.1.0", "serde", ] @@ -3039,7 +3046,11 @@ dependencies = [ "httparse", "indexmap 2.12.1", "libdd-alloc", +<<<<<<< HEAD "libdd-common 1.1.0 (git+https://github.com/DataDog/libdatadog?tag=v27.0.0)", +======= + "libdd-common 1.0.0", +>>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) "libdd-profiling-protobuf", "mime 0.3.17", "parking_lot", @@ -3170,8 +3181,13 @@ dependencies = [ "http", "http-body-util", "httpmock", +<<<<<<< HEAD "hyper 1.6.0", "indexmap 2.12.1", +======= + "hyper", + "indexmap 2.12.0", +>>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) "libdd-common 1.1.0", "libdd-tinybytes", "libdd-trace-normalization", diff --git a/components-rs/bytes.rs b/components-rs/bytes.rs index 939b2b0d802..992b08fd05a 100644 --- a/components-rs/bytes.rs +++ b/components-rs/bytes.rs @@ -150,28 +150,28 @@ fn convert_literal_to_bytes_string(string: *const c_char) -> BytesString { } #[no_mangle] -pub extern "C" fn ddog_set_span_service_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_service_zstr(ptr: &mut Span, str: &mut ZendString) { ptr.service = convert_zend_to_bytes_string(str); } #[no_mangle] -pub extern "C" fn ddog_set_span_name_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_name_zstr(ptr: &mut Span, str: &mut ZendString) { ptr.name = convert_zend_to_bytes_string(str); } #[no_mangle] -pub extern "C" fn ddog_set_span_resource_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_resource_zstr(ptr: &mut Span, str: &mut ZendString) { ptr.resource = convert_zend_to_bytes_string(str); } #[no_mangle] -pub extern "C" fn ddog_set_span_type_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_type_zstr(ptr: &mut Span, str: &mut ZendString) { ptr.r#type = convert_zend_to_bytes_string(str); } #[no_mangle] pub extern "C" fn ddog_add_span_meta_zstr( - ptr: &mut SpanBytes, + ptr: &mut Span, key: &mut ZendString, val: &mut ZendString, ) { @@ -183,7 +183,7 @@ pub extern "C" fn ddog_add_span_meta_zstr( #[no_mangle] pub extern "C" fn ddog_add_CharSlice_span_meta_zstr( - ptr: &mut SpanBytes, + ptr: &mut Span, key: CharSlice, val: &mut ZendString, ) { @@ -195,7 +195,7 @@ pub extern "C" fn ddog_add_CharSlice_span_meta_zstr( #[no_mangle] pub extern "C" fn ddog_add_zstr_span_meta_str( - ptr: &mut SpanBytes, + ptr: &mut Span, key: &mut ZendString, val: *const c_char, ) { @@ -207,7 +207,7 @@ pub extern "C" fn ddog_add_zstr_span_meta_str( #[no_mangle] pub extern "C" fn ddog_add_str_span_meta_str( - ptr: &mut SpanBytes, + ptr: &mut Span, key: *const c_char, val: *const c_char, ) { @@ -219,7 +219,7 @@ pub extern "C" fn ddog_add_str_span_meta_str( #[no_mangle] pub extern "C" fn ddog_add_str_span_meta_zstr( - ptr: &mut SpanBytes, + ptr: &mut Span, key: *const c_char, val: &mut ZendString, ) { @@ -231,7 +231,7 @@ pub extern "C" fn ddog_add_str_span_meta_zstr( #[no_mangle] pub extern "C" fn ddog_add_str_span_meta_CharSlice( - ptr: &mut SpanBytes, + ptr: &mut Span, key: *const c_char, val: CharSlice, ) { @@ -242,28 +242,28 @@ pub extern "C" fn ddog_add_str_span_meta_CharSlice( } #[no_mangle] -pub extern "C" fn ddog_del_span_meta_zstr(ptr: &mut SpanBytes, key: &mut ZendString) { +pub extern "C" fn ddog_del_span_meta_zstr(ptr: &mut Span, key: &mut ZendString) { ptr.meta.remove(&convert_zend_to_bytes_string(key)); } #[no_mangle] -pub extern "C" fn ddog_del_span_meta_str(ptr: &mut SpanBytes, key: *const c_char) { +pub extern "C" fn ddog_del_span_meta_str(ptr: &mut Span, key: *const c_char) { ptr.meta.remove(&convert_literal_to_bytes_string(key)); } #[no_mangle] -pub extern "C" fn ddog_has_span_meta_zstr(ptr: &mut SpanBytes, key: &mut ZendString) -> bool { +pub extern "C" fn ddog_has_span_meta_zstr(ptr: &mut Span, key: &mut ZendString) -> bool { ptr.meta.contains_key(&convert_zend_to_bytes_string(key)) } #[no_mangle] -pub extern "C" fn ddog_has_span_meta_str(ptr: &mut SpanBytes, key: *const c_char) -> bool { +pub extern "C" fn ddog_has_span_meta_str(ptr: &mut Span, key: *const c_char) -> bool { ptr.meta.contains_key(&convert_literal_to_bytes_string(key)) } #[no_mangle] pub extern "C" fn ddog_get_span_meta_str( - span: &mut SpanBytes, + span: &mut Span, key: *const c_char, ) -> CharSlice<'static> { match span.meta.get(&convert_literal_to_bytes_string(key)) { @@ -276,29 +276,29 @@ pub extern "C" fn ddog_get_span_meta_str( } #[no_mangle] -pub extern "C" fn ddog_add_span_metrics_zstr(ptr: &mut SpanBytes, key: &mut ZendString, val: f64) { +pub extern "C" fn ddog_add_span_metrics_zstr(ptr: &mut Span, key: &mut ZendString, val: f64) { ptr.metrics.insert(convert_zend_to_bytes_string(key), val); } #[no_mangle] -pub extern "C" fn ddog_has_span_metrics_zstr(ptr: &mut SpanBytes, key: &mut ZendString) -> bool { +pub extern "C" fn ddog_has_span_metrics_zstr(ptr: &mut Span, key: &mut ZendString) -> bool { ptr.metrics.contains_key(&convert_zend_to_bytes_string(key)) } #[no_mangle] -pub extern "C" fn ddog_del_span_metrics_zstr(ptr: &mut SpanBytes, key: &mut ZendString) { +pub extern "C" fn ddog_del_span_metrics_zstr(ptr: &mut Span, key: &mut ZendString) { ptr.metrics.remove(&convert_zend_to_bytes_string(key)); } #[no_mangle] -pub extern "C" fn ddog_add_span_metrics_str(ptr: &mut SpanBytes, key: *const c_char, val: f64) { +pub extern "C" fn ddog_add_span_metrics_str(ptr: &mut Span, key: *const c_char, val: f64) { ptr.metrics .insert(convert_literal_to_bytes_string(key), val); } #[no_mangle] pub extern "C" fn ddog_get_span_metrics_str( - ptr: &mut SpanBytes, + ptr: &mut Span, key: *const c_char, result: &mut f64, ) -> bool { @@ -312,13 +312,13 @@ pub extern "C" fn ddog_get_span_metrics_str( } #[no_mangle] -pub extern "C" fn ddog_del_span_metrics_str(ptr: &mut SpanBytes, key: *const c_char) { +pub extern "C" fn ddog_del_span_metrics_str(ptr: &mut Span, key: *const c_char) { ptr.metrics.remove(&convert_literal_to_bytes_string(key)); } #[no_mangle] pub extern "C" fn ddog_add_span_meta_struct_zstr( - ptr: &mut SpanBytes, + ptr: &mut Span, key: &mut ZendString, val: &mut ZendString, ) { @@ -328,7 +328,7 @@ pub extern "C" fn ddog_add_span_meta_struct_zstr( #[no_mangle] pub extern "C" fn ddog_add_zstr_span_meta_struct_CharSlice( - ptr: &mut SpanBytes, + ptr: &mut Span, key: &mut ZendString, val: CharSlice, ) { diff --git a/components-rs/common.h b/components-rs/common.h index 80d130b081e..4f07ebc0ec7 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -997,6 +997,19 @@ typedef struct ddog_AttributeAnyValueBytes ddog_AttributeAnyValueBytes; typedef struct ddog_AttributeArrayValueBytes ddog_AttributeArrayValueBytes; +typedef enum ddog_Method { + DDOG_METHOD_GET = 0, + DDOG_METHOD_POST = 1, + DDOG_METHOD_PUT = 2, + DDOG_METHOD_DELETE = 3, + DDOG_METHOD_PATCH = 4, + DDOG_METHOD_HEAD = 5, + DDOG_METHOD_OPTIONS = 6, + DDOG_METHOD_TRACE = 7, + DDOG_METHOD_CONNECT = 8, + DDOG_METHOD_OTHER = 9, +} ddog_Method; + typedef enum ddog_DynamicInstrumentationConfigState { DDOG_DYNAMIC_INSTRUMENTATION_CONFIG_STATE_ENABLED, DDOG_DYNAMIC_INSTRUMENTATION_CONFIG_STATE_DISABLED, diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 551298038e6..f3a586449f9 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -131,6 +131,17 @@ ddog_MaybeError ddog_sidecar_telemetry_enqueueConfig(struct ddog_SidecarTranspor ddog_CharSlice config_id, struct ddog_Option_U64 seq_id); +/** + * Reports an endpoint to the telemetry. + */ +ddog_MaybeError ddog_sidecar_telemetry_addEndpoint(struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + const ddog_QueueId *queue_id, + enum ddog_Method method, + ddog_CharSlice path, + ddog_CharSlice operation_name, + ddog_CharSlice resource_name); + /** * Reports a dependency to the telemetry. */ diff --git a/ext/coms.c b/ext/coms.c index bae9537dac9..1b0185aba40 100644 --- a/ext/coms.c +++ b/ext/coms.c @@ -799,13 +799,10 @@ 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 } } @@ -1504,7 +1501,6 @@ 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; } diff --git a/ext/sidecar.c b/ext/sidecar.c index ba7941b32e5..3db69a25294 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -140,10 +140,8 @@ void ddtrace_sidecar_update_process_tags(void) { } static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork); -#ifndef _WIN32 static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void); static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appsec_config); -#endif static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { if (!ddtrace_endpoint || !dogstatsd_endpoint) { @@ -235,7 +233,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void) { return sidecar_transport; } -#ifndef _WIN32 static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { if (!ddtrace_endpoint) { return NULL; @@ -244,7 +241,11 @@ static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { dd_set_endpoint_test_token(dogstatsd_endpoint); +#ifdef _WIN32 + char logpath[MAX_PATH]; +#else char logpath[MAXPATHLEN]; +#endif int error_fd = atomic_load(&ddtrace_error_log_fd); if (error_fd == -1 || ddtrace_get_fd_path(error_fd, logpath) < 0) { *logpath = 0; @@ -270,12 +271,14 @@ static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void) { return ddtrace_sidecar_connect_as_worker(false); } -#endif static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appsec_config) { -#ifndef _WIN32 - pid_t current_pid = getpid(); - bool is_child_process = (ddtrace_sidecar_master_pid != 0 && (int32_t)current_pid != ddtrace_sidecar_master_pid); +#ifdef _WIN32 + int32_t current_pid = (int32_t)GetCurrentProcessId(); +#else + int32_t current_pid = (int32_t)getpid(); +#endif + bool is_child_process = (ddtrace_sidecar_master_pid != 0 && current_pid != ddtrace_sidecar_master_pid); bool listener_available = ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid); @@ -298,7 +301,6 @@ static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appse } return; } -#endif ddtrace_set_non_resettable_sidecar_globals(); ddtrace_set_resettable_sidecar_globals(); @@ -326,21 +328,16 @@ static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appse } } -#ifndef _WIN32 ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { return ddtrace_sidecar_connect_as_worker(false); } -#endif ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { return ddtrace_sidecar_connect_subprocess(); - } -#ifndef _WIN32 - else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + } else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { return ddtrace_sidecar_connect_thread(); } -#endif zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); ddog_SidecarTransport *transport = NULL; @@ -354,7 +351,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { } break; -#ifndef _WIN32 case DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD: // Force thread only transport = ddtrace_sidecar_connect_thread(); @@ -362,7 +358,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { LOG(ERROR, "Thread connection failed (mode=thread, no fallback)"); } break; -#endif case DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO: default: @@ -371,7 +366,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { if (!transport) { if (ddtrace_endpoint) { -#ifndef _WIN32 // 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(); @@ -381,7 +375,6 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { } else { LOG(ERROR, "Both subprocess and thread connections failed, sidecar unavailable"); } -#endif } } break; @@ -430,12 +423,10 @@ bool ddtrace_sidecar_should_enable(bool *appsec_activation, bool *appsec_config) void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); -#ifndef _WIN32 if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD) { ddtrace_sidecar_setup_thread_mode(appsec_activation, appsec_config); return; } -#endif ddtrace_set_non_resettable_sidecar_globals(); ddtrace_set_resettable_sidecar_globals(); @@ -445,13 +436,11 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { ddtrace_sidecar = ddtrace_sidecar_connect_subprocess(); if (!ddtrace_sidecar) { -#ifndef _WIN32 if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO && ddtrace_endpoint) { LOG(WARN, "Subprocess connection failed, falling back to thread mode"); ddtrace_sidecar_setup_thread_mode(appsec_activation, appsec_config); return; } -#endif if (ddtrace_endpoint) { dd_free_endpoints(); @@ -462,8 +451,11 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { } void ddtrace_sidecar_minit(void) { -#ifndef _WIN32 +#ifdef _WIN32 + ddtrace_sidecar_master_pid = (int32_t)GetCurrentProcessId(); +#else ddtrace_sidecar_master_pid = (int32_t)getpid(); +#endif zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); @@ -471,7 +463,6 @@ void ddtrace_sidecar_minit(void) { ddtrace_ffi_try("Starting sidecar master listener in MINIT", ddog_sidecar_connect_master(ddtrace_sidecar_master_pid)); } -#endif } void ddtrace_sidecar_handle_fork(void) { diff --git a/ext/sidecar.h b/ext/sidecar.h index a337187d409..13384b3bdab 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -31,9 +31,7 @@ DDTRACE_PUBLIC struct telemetry_rc_info ddtrace_get_telemetry_rc_info(void); // Connection functions ddog_SidecarTransport *ddtrace_sidecar_connect_subprocess(void); -#ifndef _WIN32 ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void); -#endif ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork); // Lifecycle functions diff --git a/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php b/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php index 3cf39a6908a..beb733eb2cf 100644 --- a/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php +++ b/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php @@ -41,10 +41,6 @@ public function testThreadModeTracesAreSubmitted() $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 () { @@ -67,10 +63,6 @@ public function testThreadModeMultipleRequests() $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 () { From e4a603a2df91b2426d4cabe2b126f1c6c45e9d75 Mon Sep 17 00:00:00 2001 From: Alexandre Rulleau Date: Wed, 18 Feb 2026 14:25:24 +0100 Subject: [PATCH 10/10] chore: apply feedbacks Signed-off-by: Alexandre Rulleau --- Cargo.lock | 16 ---- appsec/third_party/libddwaf | 2 +- components-rs/bytes.rs | 46 ++++----- components-rs/common.h | 14 +-- ext/ddtrace_arginfo.h | 3 - ext/sidecar.c | 179 +++++++++++++++--------------------- ext/sidecar.h | 2 - 7 files changed, 97 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb1bc4922df..588b1468b4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2814,7 +2814,6 @@ dependencies = [ ] [[package]] -<<<<<<< HEAD name = "libdd-common" version = "1.1.0" source = "git+https://github.com/DataDog/libdatadog?tag=v27.0.0#72e56a3dcf9189a92db1f177c4c9d844725079f7" @@ -2853,8 +2852,6 @@ dependencies = [ ] [[package]] -======= ->>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) name = "libdd-common-ffi" version = "0.0.1" dependencies = [ @@ -2865,11 +2862,7 @@ dependencies = [ "chrono", "crossbeam-queue", "function_name", -<<<<<<< HEAD "hyper 1.6.0", -======= - "hyper", ->>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) "libdd-common 1.1.0", "serde", ] @@ -3046,11 +3039,7 @@ dependencies = [ "httparse", "indexmap 2.12.1", "libdd-alloc", -<<<<<<< HEAD "libdd-common 1.1.0 (git+https://github.com/DataDog/libdatadog?tag=v27.0.0)", -======= - "libdd-common 1.0.0", ->>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) "libdd-profiling-protobuf", "mime 0.3.17", "parking_lot", @@ -3181,13 +3170,8 @@ dependencies = [ "http", "http-body-util", "httpmock", -<<<<<<< HEAD "hyper 1.6.0", "indexmap 2.12.1", -======= - "hyper", - "indexmap 2.12.0", ->>>>>>> ec704d7cc (feat(sidecar): support threaded connection for windows) "libdd-common 1.1.0", "libdd-tinybytes", "libdd-trace-normalization", diff --git a/appsec/third_party/libddwaf b/appsec/third_party/libddwaf index 00e895f2c50..2f4aa84cd61 160000 --- a/appsec/third_party/libddwaf +++ b/appsec/third_party/libddwaf @@ -1 +1 @@ -Subproject commit 00e895f2c507a714062aa88ed41466aec10d2e01 +Subproject commit 2f4aa84cd61dc13229d1431779c007bf4ebda89c diff --git a/components-rs/bytes.rs b/components-rs/bytes.rs index 992b08fd05a..939b2b0d802 100644 --- a/components-rs/bytes.rs +++ b/components-rs/bytes.rs @@ -150,28 +150,28 @@ fn convert_literal_to_bytes_string(string: *const c_char) -> BytesString { } #[no_mangle] -pub extern "C" fn ddog_set_span_service_zstr(ptr: &mut Span, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_service_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { ptr.service = convert_zend_to_bytes_string(str); } #[no_mangle] -pub extern "C" fn ddog_set_span_name_zstr(ptr: &mut Span, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_name_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { ptr.name = convert_zend_to_bytes_string(str); } #[no_mangle] -pub extern "C" fn ddog_set_span_resource_zstr(ptr: &mut Span, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_resource_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { ptr.resource = convert_zend_to_bytes_string(str); } #[no_mangle] -pub extern "C" fn ddog_set_span_type_zstr(ptr: &mut Span, str: &mut ZendString) { +pub extern "C" fn ddog_set_span_type_zstr(ptr: &mut SpanBytes, str: &mut ZendString) { ptr.r#type = convert_zend_to_bytes_string(str); } #[no_mangle] pub extern "C" fn ddog_add_span_meta_zstr( - ptr: &mut Span, + ptr: &mut SpanBytes, key: &mut ZendString, val: &mut ZendString, ) { @@ -183,7 +183,7 @@ pub extern "C" fn ddog_add_span_meta_zstr( #[no_mangle] pub extern "C" fn ddog_add_CharSlice_span_meta_zstr( - ptr: &mut Span, + ptr: &mut SpanBytes, key: CharSlice, val: &mut ZendString, ) { @@ -195,7 +195,7 @@ pub extern "C" fn ddog_add_CharSlice_span_meta_zstr( #[no_mangle] pub extern "C" fn ddog_add_zstr_span_meta_str( - ptr: &mut Span, + ptr: &mut SpanBytes, key: &mut ZendString, val: *const c_char, ) { @@ -207,7 +207,7 @@ pub extern "C" fn ddog_add_zstr_span_meta_str( #[no_mangle] pub extern "C" fn ddog_add_str_span_meta_str( - ptr: &mut Span, + ptr: &mut SpanBytes, key: *const c_char, val: *const c_char, ) { @@ -219,7 +219,7 @@ pub extern "C" fn ddog_add_str_span_meta_str( #[no_mangle] pub extern "C" fn ddog_add_str_span_meta_zstr( - ptr: &mut Span, + ptr: &mut SpanBytes, key: *const c_char, val: &mut ZendString, ) { @@ -231,7 +231,7 @@ pub extern "C" fn ddog_add_str_span_meta_zstr( #[no_mangle] pub extern "C" fn ddog_add_str_span_meta_CharSlice( - ptr: &mut Span, + ptr: &mut SpanBytes, key: *const c_char, val: CharSlice, ) { @@ -242,28 +242,28 @@ pub extern "C" fn ddog_add_str_span_meta_CharSlice( } #[no_mangle] -pub extern "C" fn ddog_del_span_meta_zstr(ptr: &mut Span, key: &mut ZendString) { +pub extern "C" fn ddog_del_span_meta_zstr(ptr: &mut SpanBytes, key: &mut ZendString) { ptr.meta.remove(&convert_zend_to_bytes_string(key)); } #[no_mangle] -pub extern "C" fn ddog_del_span_meta_str(ptr: &mut Span, key: *const c_char) { +pub extern "C" fn ddog_del_span_meta_str(ptr: &mut SpanBytes, key: *const c_char) { ptr.meta.remove(&convert_literal_to_bytes_string(key)); } #[no_mangle] -pub extern "C" fn ddog_has_span_meta_zstr(ptr: &mut Span, key: &mut ZendString) -> bool { +pub extern "C" fn ddog_has_span_meta_zstr(ptr: &mut SpanBytes, key: &mut ZendString) -> bool { ptr.meta.contains_key(&convert_zend_to_bytes_string(key)) } #[no_mangle] -pub extern "C" fn ddog_has_span_meta_str(ptr: &mut Span, key: *const c_char) -> bool { +pub extern "C" fn ddog_has_span_meta_str(ptr: &mut SpanBytes, key: *const c_char) -> bool { ptr.meta.contains_key(&convert_literal_to_bytes_string(key)) } #[no_mangle] pub extern "C" fn ddog_get_span_meta_str( - span: &mut Span, + span: &mut SpanBytes, key: *const c_char, ) -> CharSlice<'static> { match span.meta.get(&convert_literal_to_bytes_string(key)) { @@ -276,29 +276,29 @@ pub extern "C" fn ddog_get_span_meta_str( } #[no_mangle] -pub extern "C" fn ddog_add_span_metrics_zstr(ptr: &mut Span, key: &mut ZendString, val: f64) { +pub extern "C" fn ddog_add_span_metrics_zstr(ptr: &mut SpanBytes, key: &mut ZendString, val: f64) { ptr.metrics.insert(convert_zend_to_bytes_string(key), val); } #[no_mangle] -pub extern "C" fn ddog_has_span_metrics_zstr(ptr: &mut Span, key: &mut ZendString) -> bool { +pub extern "C" fn ddog_has_span_metrics_zstr(ptr: &mut SpanBytes, key: &mut ZendString) -> bool { ptr.metrics.contains_key(&convert_zend_to_bytes_string(key)) } #[no_mangle] -pub extern "C" fn ddog_del_span_metrics_zstr(ptr: &mut Span, key: &mut ZendString) { +pub extern "C" fn ddog_del_span_metrics_zstr(ptr: &mut SpanBytes, key: &mut ZendString) { ptr.metrics.remove(&convert_zend_to_bytes_string(key)); } #[no_mangle] -pub extern "C" fn ddog_add_span_metrics_str(ptr: &mut Span, key: *const c_char, val: f64) { +pub extern "C" fn ddog_add_span_metrics_str(ptr: &mut SpanBytes, key: *const c_char, val: f64) { ptr.metrics .insert(convert_literal_to_bytes_string(key), val); } #[no_mangle] pub extern "C" fn ddog_get_span_metrics_str( - ptr: &mut Span, + ptr: &mut SpanBytes, key: *const c_char, result: &mut f64, ) -> bool { @@ -312,13 +312,13 @@ pub extern "C" fn ddog_get_span_metrics_str( } #[no_mangle] -pub extern "C" fn ddog_del_span_metrics_str(ptr: &mut Span, key: *const c_char) { +pub extern "C" fn ddog_del_span_metrics_str(ptr: &mut SpanBytes, key: *const c_char) { ptr.metrics.remove(&convert_literal_to_bytes_string(key)); } #[no_mangle] pub extern "C" fn ddog_add_span_meta_struct_zstr( - ptr: &mut Span, + ptr: &mut SpanBytes, key: &mut ZendString, val: &mut ZendString, ) { @@ -328,7 +328,7 @@ pub extern "C" fn ddog_add_span_meta_struct_zstr( #[no_mangle] pub extern "C" fn ddog_add_zstr_span_meta_struct_CharSlice( - ptr: &mut Span, + ptr: &mut SpanBytes, key: &mut ZendString, val: CharSlice, ) { diff --git a/components-rs/common.h b/components-rs/common.h index 4f07ebc0ec7..cc3e8b041e3 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -356,6 +356,7 @@ typedef enum ddog_RemoteConfigProduct { DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, + DDOG_REMOTE_CONFIG_PRODUCT_FFE_FLAGS, DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, } ddog_RemoteConfigProduct; @@ -1016,19 +1017,6 @@ typedef enum ddog_DynamicInstrumentationConfigState { DDOG_DYNAMIC_INSTRUMENTATION_CONFIG_STATE_NOT_SET, } ddog_DynamicInstrumentationConfigState; -typedef enum ddog_Method { - DDOG_METHOD_GET = 0, - DDOG_METHOD_POST = 1, - DDOG_METHOD_PUT = 2, - DDOG_METHOD_DELETE = 3, - DDOG_METHOD_PATCH = 4, - DDOG_METHOD_HEAD = 5, - DDOG_METHOD_OPTIONS = 6, - DDOG_METHOD_TRACE = 7, - DDOG_METHOD_CONNECT = 8, - DDOG_METHOD_OTHER = 9, -} ddog_Method; - typedef struct ddog_AgentInfoReader ddog_AgentInfoReader; typedef struct ddog_AgentRemoteConfigReader ddog_AgentRemoteConfigReader; diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 8f64ec9c1e5..f5dbd2a5046 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -560,9 +560,6 @@ 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); diff --git a/ext/sidecar.c b/ext/sidecar.c index 3db69a25294..89de0b5600f 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -198,42 +198,7 @@ static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { } -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); - - ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_SUBPROCESS; - - return sidecar_transport; -} - -static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { +static ddog_SidecarTransport *dd_sidecar_connect(bool as_worker, bool is_fork) { if (!ddtrace_endpoint) { return NULL; } @@ -243,6 +208,9 @@ static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { #ifdef _WIN32 char logpath[MAX_PATH]; + if (!as_worker) { + DDOG_PHP_FUNCTION = (const uint8_t *)zend_hash_func; + } #else char logpath[MAXPATHLEN]; #endif @@ -252,26 +220,30 @@ static ddog_SidecarTransport *ddtrace_sidecar_connect_as_worker(bool is_fork) { } 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; + if (as_worker) { + 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; + } + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; + } else { + 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; + } + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_SUBPROCESS; } dd_sidecar_post_connect(&sidecar_transport, is_fork, logpath); - ddtrace_sidecar_reconnect(&sidecar_transport, dd_sidecar_connection_factory_thread); - - ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; - return sidecar_transport; } -// Connection factory for thread mode - connects as worker to master listener -static ddog_SidecarTransport *dd_sidecar_connection_factory_thread(void) { - return ddtrace_sidecar_connect_as_worker(false); -} - static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appsec_config) { #ifdef _WIN32 int32_t current_pid = (int32_t)GetCurrentProcessId(); @@ -283,12 +255,7 @@ static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appse 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); - - ddtrace_sidecar = ddtrace_sidecar_connect_as_worker(false); + ddtrace_sidecar = dd_sidecar_connect(true, false); if (!ddtrace_sidecar) { LOG(WARN, "Failed to connect worker to sidecar (PID=%d, master=%d)", (int32_t)current_pid, ddtrace_sidecar_master_pid); @@ -302,11 +269,6 @@ static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appse 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); - 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) { @@ -317,7 +279,7 @@ static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appse LOG(INFO, "Started sidecar master listener thread (PID=%d)", ddtrace_sidecar_master_pid); - ddtrace_sidecar = ddtrace_sidecar_connect_as_worker(false); + ddtrace_sidecar = dd_sidecar_connect(true, false); if (!ddtrace_sidecar) { LOG(WARN, "Failed to connect master process to sidecar"); return; @@ -328,15 +290,11 @@ static void ddtrace_sidecar_setup_thread_mode(bool appsec_activation, bool appse } } -ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { - return ddtrace_sidecar_connect_as_worker(false); -} - ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { - return ddtrace_sidecar_connect_subprocess(); + return dd_sidecar_connect(false, is_fork); } else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { - return ddtrace_sidecar_connect_thread(); + return dd_sidecar_connect(true, is_fork); } zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); @@ -345,7 +303,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { switch (mode) { case DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS: // Force subprocess only - transport = ddtrace_sidecar_connect_subprocess(); + transport = dd_sidecar_connect(false, is_fork); if (!transport) { LOG(ERROR, "Subprocess connection failed (mode=subprocess, no fallback)"); } @@ -353,7 +311,7 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { case DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD: // Force thread only - transport = ddtrace_sidecar_connect_thread(); + transport = dd_sidecar_connect(true, is_fork); if (!transport) { LOG(ERROR, "Thread connection failed (mode=thread, no fallback)"); } @@ -362,13 +320,12 @@ ddog_SidecarTransport *ddtrace_sidecar_connect(bool is_fork) { case DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO: default: // Try subprocess first, fallback to thread if needed - transport = ddtrace_sidecar_connect_subprocess(); + transport = dd_sidecar_connect(false, is_fork); if (!transport) { if (ddtrace_endpoint) { - // 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(); + transport = dd_sidecar_connect(true, is_fork); if (transport) { LOG(INFO, "Connected to sidecar via thread (fallback)"); @@ -421,6 +378,11 @@ bool ddtrace_sidecar_should_enable(bool *appsec_activation, bool *appsec_config) } void ddtrace_sidecar_setup(bool appsec_activation, bool appsec_config) { + 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); + zend_long mode = get_global_DD_TRACE_SIDECAR_CONNECTION_MODE(); if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD) { @@ -428,12 +390,7 @@ void ddtrace_sidecar_setup(bool appsec_activation, bool 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_subprocess(); + ddtrace_sidecar = dd_sidecar_connect(false, false); if (!ddtrace_sidecar) { if (mode == DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO && ddtrace_endpoint) { @@ -475,45 +432,51 @@ void ddtrace_sidecar_handle_fork(void) { return; } - // Handle thread mode fork - reconnect to parent's listener + ddtrace_force_new_instance_id(); + + if (ddtrace_sidecar) { + ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; + } + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { ddtrace_ffi_try("Failed clearing inherited listener state", ddog_sidecar_clear_inherited_listener()); - if (!ddog_sidecar_is_master_listener_active(ddtrace_sidecar_master_pid)) { - LOG(WARN, "Child process cannot reconnect: parent's listener not active"); - return; - } - - ddtrace_force_new_instance_id(); - - // Attempt to connect as a worker to parent's listener - ddtrace_sidecar = ddtrace_sidecar_connect_as_worker(true); - if (!ddtrace_sidecar) { - LOG(WARN, "Failed to connect child to parent's sidecar listener (child PID=%d, parent=%d)", + // Try to connect as a worker to parent's listener + ddtrace_sidecar = dd_sidecar_connect(true, true); + if (ddtrace_sidecar) { + LOG(INFO, "Child process reconnected to parent's sidecar listener after fork (child PID=%d, parent=%d)", (int32_t)getpid(), ddtrace_sidecar_master_pid); return; } - LOG(INFO, "Child process reconnected to parent's sidecar listener after fork (child PID=%d, parent=%d)", + // Parent's listener not available, fall back to starting a new master in this process + LOG(INFO, "Parent's sidecar listener not available after fork (child PID=%d, parent=%d), starting new master", (int32_t)getpid(), ddtrace_sidecar_master_pid); - } - // Handle subprocess mode fork - reset and spawn new subprocess - else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { - ddtrace_force_new_instance_id(); - if (ddtrace_sidecar) { - ddog_sidecar_transport_drop(ddtrace_sidecar); - ddtrace_sidecar = NULL; + ddtrace_sidecar_master_pid = (int32_t)getpid(); + if (!ddtrace_ffi_try("Failed starting sidecar master listener in child process", + ddog_sidecar_connect_master((int32_t)ddtrace_sidecar_master_pid))) { + if (ddtrace_endpoint) { + dd_free_endpoints(); + } + return; + } - ddtrace_sidecar = ddtrace_sidecar_connect(true); - if (!ddtrace_sidecar) { - if (ddtrace_endpoint) { - dd_free_endpoints(); - } - } else { - ddtrace_sidecar_submit_root_span_data(); + ddtrace_sidecar = dd_sidecar_connect(true, false); + if (!ddtrace_sidecar) { + LOG(WARN, "Failed to connect to new sidecar master in child process (PID=%d)", + (int32_t)getpid()); + } + } else if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_SUBPROCESS) { + ddtrace_sidecar = ddtrace_sidecar_connect(true); + if (!ddtrace_sidecar) { + if (ddtrace_endpoint) { + dd_free_endpoints(); } + } else { + ddtrace_sidecar_submit_root_span_data(); } } #endif @@ -546,9 +509,12 @@ 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 +#ifdef _WIN32 + int32_t current_pid = (int32_t)GetCurrentProcessId(); +#else int32_t current_pid = (int32_t)getpid(); +#endif if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD && ddtrace_sidecar_master_pid != 0 && current_pid == ddtrace_sidecar_master_pid) { @@ -563,7 +529,6 @@ void ddtrace_sidecar_shutdown(void) { ddtrace_ffi_try("Failed shutting down master listener", ddog_sidecar_shutdown_master_listener()); } -#endif // Standard cleanup if (ddtrace_sidecar_instance_id) { diff --git a/ext/sidecar.h b/ext/sidecar.h index 13384b3bdab..b18bea433e2 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -30,8 +30,6 @@ 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(bool is_fork); // Lifecycle functions