diff --git a/components-rs/common.h b/components-rs/common.h index bfe1502bce3..43e6a195f62 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -264,15 +264,19 @@ typedef struct _zend_string _zend_string; #define ddog_MultiTargetFetcher_DEFAULT_CLIENTS_LIMIT 100 -typedef enum ddog_ConfigurationOrigin { - DDOG_CONFIGURATION_ORIGIN_ENV_VAR, - DDOG_CONFIGURATION_ORIGIN_CODE, - DDOG_CONFIGURATION_ORIGIN_DD_CONFIG, - DDOG_CONFIGURATION_ORIGIN_REMOTE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_DEFAULT, - DDOG_CONFIGURATION_ORIGIN_LOCAL_STABLE_CONFIG, - DDOG_CONFIGURATION_ORIGIN_FLEET_STABLE_CONFIG, -} ddog_ConfigurationOrigin; +typedef enum ddog_Log { + DDOG_LOG_ERROR = 1, + DDOG_LOG_WARN = 2, + DDOG_LOG_INFO = 3, + DDOG_LOG_DEBUG = 4, + DDOG_LOG_TRACE = 5, + DDOG_LOG_DEPRECATED = (3 | ddog_LOG_ONCE), + DDOG_LOG_STARTUP = (3 | (2 << 4)), + DDOG_LOG_STARTUP_WARN = (1 | (2 << 4)), + DDOG_LOG_SPAN = (4 | (3 << 4)), + DDOG_LOG_SPAN_TRACE = (5 | (3 << 4)), + DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), +} ddog_Log; typedef enum ddog_DynamicConfigUpdateMode { DDOG_DYNAMIC_CONFIG_UPDATE_MODE_READ, @@ -281,30 +285,16 @@ typedef enum ddog_DynamicConfigUpdateMode { DDOG_DYNAMIC_CONFIG_UPDATE_MODE_RESTORE, } ddog_DynamicConfigUpdateMode; -typedef enum ddog_EvaluateAt { - DDOG_EVALUATE_AT_ENTRY, - DDOG_EVALUATE_AT_EXIT, -} ddog_EvaluateAt; - typedef enum ddog_InBodyLocation { DDOG_IN_BODY_LOCATION_NONE, DDOG_IN_BODY_LOCATION_START, DDOG_IN_BODY_LOCATION_END, } ddog_InBodyLocation; -typedef enum ddog_Log { - DDOG_LOG_ERROR = 1, - DDOG_LOG_WARN = 2, - DDOG_LOG_INFO = 3, - DDOG_LOG_DEBUG = 4, - DDOG_LOG_TRACE = 5, - DDOG_LOG_DEPRECATED = (3 | ddog_LOG_ONCE), - DDOG_LOG_STARTUP = (3 | (2 << 4)), - DDOG_LOG_STARTUP_WARN = (1 | (2 << 4)), - DDOG_LOG_SPAN = (4 | (3 << 4)), - DDOG_LOG_SPAN_TRACE = (5 | (3 << 4)), - DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), -} ddog_Log; +typedef enum ddog_EvaluateAt { + DDOG_EVALUATE_AT_ENTRY, + DDOG_EVALUATE_AT_EXIT, +} ddog_EvaluateAt; typedef enum ddog_MetricKind { DDOG_METRIC_KIND_COUNT, @@ -313,6 +303,37 @@ typedef enum ddog_MetricKind { DDOG_METRIC_KIND_DISTRIBUTION, } ddog_MetricKind; +typedef enum ddog_SpanProbeTarget { + DDOG_SPAN_PROBE_TARGET_ACTIVE, + DDOG_SPAN_PROBE_TARGET_ROOT, +} ddog_SpanProbeTarget; + +typedef enum ddog_ProbeStatus { + DDOG_PROBE_STATUS_RECEIVED, + DDOG_PROBE_STATUS_INSTALLED, + DDOG_PROBE_STATUS_EMITTING, + DDOG_PROBE_STATUS_ERROR, + DDOG_PROBE_STATUS_BLOCKED, + DDOG_PROBE_STATUS_WARNING, +} ddog_ProbeStatus; + +typedef enum ddog_ConfigurationOrigin { + DDOG_CONFIGURATION_ORIGIN_ENV_VAR, + DDOG_CONFIGURATION_ORIGIN_CODE, + DDOG_CONFIGURATION_ORIGIN_DD_CONFIG, + DDOG_CONFIGURATION_ORIGIN_REMOTE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_DEFAULT, + DDOG_CONFIGURATION_ORIGIN_LOCAL_STABLE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_FLEET_STABLE_CONFIG, + DDOG_CONFIGURATION_ORIGIN_CALCULATED, +} ddog_ConfigurationOrigin; + +typedef enum ddog_MetricType { + DDOG_METRIC_TYPE_GAUGE, + DDOG_METRIC_TYPE_COUNT, + DDOG_METRIC_TYPE_DISTRIBUTION, +} ddog_MetricType; + typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_TRACERS, DDOG_METRIC_NAMESPACE_PROFILERS, @@ -327,20 +348,16 @@ typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_SIDECAR, } ddog_MetricNamespace; -typedef enum ddog_MetricType { - DDOG_METRIC_TYPE_GAUGE, - DDOG_METRIC_TYPE_COUNT, - DDOG_METRIC_TYPE_DISTRIBUTION, -} ddog_MetricType; - -typedef enum ddog_ProbeStatus { - DDOG_PROBE_STATUS_RECEIVED, - DDOG_PROBE_STATUS_INSTALLED, - DDOG_PROBE_STATUS_EMITTING, - DDOG_PROBE_STATUS_ERROR, - DDOG_PROBE_STATUS_BLOCKED, - DDOG_PROBE_STATUS_WARNING, -} ddog_ProbeStatus; +typedef enum ddog_RemoteConfigProduct { + DDOG_REMOTE_CONFIG_PRODUCT_AGENT_CONFIG, + DDOG_REMOTE_CONFIG_PRODUCT_AGENT_TASK, + DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, + DDOG_REMOTE_CONFIG_PRODUCT_ASM, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, + DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, +} ddog_RemoteConfigProduct; typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_ACTIVATION = 1, @@ -388,22 +405,6 @@ typedef enum ddog_RemoteConfigCapabilities { DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_TRACE_TAGGING_RULES = 43, } ddog_RemoteConfigCapabilities; -typedef enum ddog_RemoteConfigProduct { - DDOG_REMOTE_CONFIG_PRODUCT_AGENT_CONFIG, - DDOG_REMOTE_CONFIG_PRODUCT_AGENT_TASK, - DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, - DDOG_REMOTE_CONFIG_PRODUCT_ASM, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, - DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, - DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, -} ddog_RemoteConfigProduct; - -typedef enum ddog_SpanProbeTarget { - DDOG_SPAN_PROBE_TARGET_ACTIVE, - DDOG_SPAN_PROBE_TARGET_ROOT, -} ddog_SpanProbeTarget; - typedef struct ddog_DebuggerPayload ddog_DebuggerPayload; typedef struct ddog_DslString ddog_DslString; @@ -774,17 +775,18 @@ typedef struct ddog_DebuggerValue ddog_DebuggerValue; #define ddog_EVALUATOR_RESULT_REDACTED (const void*)-2 -typedef enum ddog_DebuggerType { - DDOG_DEBUGGER_TYPE_DIAGNOSTICS, - DDOG_DEBUGGER_TYPE_LOGS, -} ddog_DebuggerType; - typedef enum ddog_FieldType { DDOG_FIELD_TYPE_STATIC, DDOG_FIELD_TYPE_ARG, DDOG_FIELD_TYPE_LOCAL, } ddog_FieldType; +typedef enum ddog_DebuggerType { + DDOG_DEBUGGER_TYPE_DIAGNOSTICS, + DDOG_DEBUGGER_TYPE_SNAPSHOTS, + DDOG_DEBUGGER_TYPE_LOGS, +} ddog_DebuggerType; + typedef struct ddog_Entry ddog_Entry; typedef struct ddog_HashMap_CowStr__Value ddog_HashMap_CowStr__Value; @@ -913,16 +915,6 @@ typedef struct ddog_OwnedCharSlice { void (*free)(ddog_CharSlice); } ddog_OwnedCharSlice; -typedef enum ddog_LogLevel { - DDOG_LOG_LEVEL_ERROR, - DDOG_LOG_LEVEL_WARN, - DDOG_LOG_LEVEL_DEBUG, -} ddog_LogLevel; - -typedef enum ddog_TelemetryWorkerBuilderBoolProperty { - DDOG_TELEMETRY_WORKER_BUILDER_BOOL_PROPERTY_CONFIG_TELEMETRY_DEBUG_LOGGING_ENABLED, -} ddog_TelemetryWorkerBuilderBoolProperty; - typedef enum ddog_TelemetryWorkerBuilderEndpointProperty { DDOG_TELEMETRY_WORKER_BUILDER_ENDPOINT_PROPERTY_CONFIG_ENDPOINT, } ddog_TelemetryWorkerBuilderEndpointProperty; @@ -941,6 +933,16 @@ typedef enum ddog_TelemetryWorkerBuilderStrProperty { DDOG_TELEMETRY_WORKER_BUILDER_STR_PROPERTY_RUNTIME_ID, } ddog_TelemetryWorkerBuilderStrProperty; +typedef enum ddog_TelemetryWorkerBuilderBoolProperty { + DDOG_TELEMETRY_WORKER_BUILDER_BOOL_PROPERTY_CONFIG_TELEMETRY_DEBUG_LOGGING_ENABLED, +} ddog_TelemetryWorkerBuilderBoolProperty; + +typedef enum ddog_LogLevel { + DDOG_LOG_LEVEL_ERROR, + DDOG_LOG_LEVEL_WARN, + DDOG_LOG_LEVEL_DEBUG, +} ddog_LogLevel; + typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder; /** @@ -954,6 +956,20 @@ typedef struct ddog_TelemetryWorkerBuilder ddog_TelemetryWorkerBuilder; */ typedef struct ddog_TelemetryWorkerHandle ddog_TelemetryWorkerHandle; +typedef enum ddog_Option_U64_Tag { + DDOG_OPTION_U64_SOME_U64, + DDOG_OPTION_U64_NONE_U64, +} ddog_Option_U64_Tag; + +typedef struct ddog_Option_U64 { + ddog_Option_U64_Tag tag; + union { + struct { + uint64_t some; + }; + }; +} ddog_Option_U64; + typedef enum ddog_Option_Bool_Tag { DDOG_OPTION_BOOL_SOME_BOOL, DDOG_OPTION_BOOL_NONE_BOOL, @@ -1065,37 +1081,28 @@ typedef struct ddog_SenderParameters { ddog_CharSlice url; } ddog_SenderParameters; -typedef enum ddog_crasht_BuildIdType { - DDOG_CRASHT_BUILD_ID_TYPE_GNU, - DDOG_CRASHT_BUILD_ID_TYPE_GO, - DDOG_CRASHT_BUILD_ID_TYPE_PDB, - DDOG_CRASHT_BUILD_ID_TYPE_SHA1, -} ddog_crasht_BuildIdType; - /** - * Result type for runtime callback registration + * Stacktrace collection occurs in the context of a crashing process. + * If the stack is sufficiently corruputed, it is possible (but unlikely), + * for stack trace collection itself to crash. + * We recommend fully enabling stacktrace collection, but having an environment + * variable to allow downgrading the collector. */ -typedef enum ddog_crasht_CallbackResult { - DDOG_CRASHT_CALLBACK_RESULT_OK, - DDOG_CRASHT_CALLBACK_RESULT_ERROR, -} ddog_crasht_CallbackResult; - -typedef enum ddog_crasht_DemangleOptions { - DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, - DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, -} ddog_crasht_DemangleOptions; - -typedef enum ddog_crasht_ErrorKind { - DDOG_CRASHT_ERROR_KIND_PANIC, - DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION, - DDOG_CRASHT_ERROR_KIND_UNIX_SIGNAL, -} ddog_crasht_ErrorKind; - -typedef enum ddog_crasht_FileType { - DDOG_CRASHT_FILE_TYPE_APK, - DDOG_CRASHT_FILE_TYPE_ELF, - DDOG_CRASHT_FILE_TYPE_PE, -} ddog_crasht_FileType; +typedef enum ddog_crasht_StacktraceCollection { + /** + * Stacktrace collection occurs in the + */ + DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, + DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, + /** + * This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information + * and also unwind inlined functions. Enabling this feature will not only provide symbolic + * details, but may also yield additional or less stack frames compared to other + * configurations. + */ + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, +} ddog_crasht_StacktraceCollection; /** * This enum represents operations a the tracked library might be engaged in. @@ -1120,6 +1127,12 @@ typedef enum ddog_crasht_OpTypes { DDOG_CRASHT_OP_TYPES_SIZE, } ddog_crasht_OpTypes; +typedef enum ddog_crasht_ErrorKind { + DDOG_CRASHT_ERROR_KIND_PANIC, + DDOG_CRASHT_ERROR_KIND_UNHANDLED_EXCEPTION, + DDOG_CRASHT_ERROR_KIND_UNIX_SIGNAL, +} ddog_crasht_ErrorKind; + /** * See https://man7.org/linux/man-pages/man2/sigaction.2.html * MUST REMAIN IN SYNC WITH THE ENUM IN emit_sigcodes.c @@ -1192,28 +1205,31 @@ typedef enum ddog_crasht_SignalNames { DDOG_CRASHT_SIGNAL_NAMES_UNKNOWN, } ddog_crasht_SignalNames; +typedef enum ddog_crasht_BuildIdType { + DDOG_CRASHT_BUILD_ID_TYPE_GNU, + DDOG_CRASHT_BUILD_ID_TYPE_GO, + DDOG_CRASHT_BUILD_ID_TYPE_PDB, + DDOG_CRASHT_BUILD_ID_TYPE_SHA1, +} ddog_crasht_BuildIdType; + +typedef enum ddog_crasht_FileType { + DDOG_CRASHT_FILE_TYPE_APK, + DDOG_CRASHT_FILE_TYPE_ELF, + DDOG_CRASHT_FILE_TYPE_PE, +} ddog_crasht_FileType; + +typedef enum ddog_crasht_DemangleOptions { + DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, + DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, +} ddog_crasht_DemangleOptions; + /** - * Stacktrace collection occurs in the context of a crashing process. - * If the stack is sufficiently corruputed, it is possible (but unlikely), - * for stack trace collection itself to crash. - * We recommend fully enabling stacktrace collection, but having an environment - * variable to allow downgrading the collector. + * Result type for runtime callback registration */ -typedef enum ddog_crasht_StacktraceCollection { - /** - * Stacktrace collection occurs in the - */ - DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, - DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, - /** - * This option uses `backtrace::resolve_frame_unsynchronized()` to gather symbol information - * and also unwind inlined functions. Enabling this feature will not only provide symbolic - * details, but may also yield additional or less stack frames compared to other - * configurations. - */ - DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, - DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, -} ddog_crasht_StacktraceCollection; +typedef enum ddog_crasht_CallbackResult { + DDOG_CRASHT_CALLBACK_RESULT_OK, + DDOG_CRASHT_CALLBACK_RESULT_ERROR, +} ddog_crasht_CallbackResult; typedef struct ddog_crasht_CrashInfo ddog_crasht_CrashInfo; @@ -1291,7 +1307,7 @@ typedef struct ddog_crasht_Config { /** * Timeout in milliseconds before the signal handler starts tearing things down to return. * If 0, uses the default timeout as specified in - * `datadog_crashtracker::shared::constants::DD_CRASHTRACK_DEFAULT_TIMEOUT`. Otherwise, uses + * `libdd_crashtracker::shared::constants::DD_CRASHTRACK_DEFAULT_TIMEOUT`. Otherwise, uses * the specified timeout value. * This is given as a uint32_t, but the actual timeout needs to fit inside of an i32 (max * 2^31-1). This is a limitation of the various interfaces used to guarantee the timeout. @@ -1411,13 +1427,17 @@ typedef struct ddog_crasht_CrashInfoBuilder_NewResult { }; } ddog_crasht_CrashInfoBuilder_NewResult; -typedef enum ddog_crasht_CrashInfo_NewResult_Tag { - DDOG_CRASHT_CRASH_INFO_NEW_RESULT_OK, - DDOG_CRASHT_CRASH_INFO_NEW_RESULT_ERR, -} ddog_crasht_CrashInfo_NewResult_Tag; +/** + * A generic result type for when an operation may fail, + * or may return in case of success. + */ +typedef enum ddog_crasht_Result_HandleCrashInfo_Tag { + DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_OK_HANDLE_CRASH_INFO, + DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_ERR_HANDLE_CRASH_INFO, +} ddog_crasht_Result_HandleCrashInfo_Tag; -typedef struct ddog_crasht_CrashInfo_NewResult { - ddog_crasht_CrashInfo_NewResult_Tag tag; +typedef struct ddog_crasht_Result_HandleCrashInfo { + ddog_crasht_Result_HandleCrashInfo_Tag tag; union { struct { struct ddog_crasht_Handle_CrashInfo ok; @@ -1426,7 +1446,9 @@ typedef struct ddog_crasht_CrashInfo_NewResult { struct ddog_Error err; }; }; -} ddog_crasht_CrashInfo_NewResult; +} ddog_crasht_Result_HandleCrashInfo; + +typedef struct ddog_crasht_Result_HandleCrashInfo ddog_crasht_CrashInfo_NewResult; typedef struct ddog_crasht_OsInfo { ddog_CharSlice architecture; diff --git a/components-rs/crashtracker.h b/components-rs/crashtracker.h index 42209ff18cc..96ed333d622 100644 --- a/components-rs/crashtracker.h +++ b/components-rs/crashtracker.h @@ -451,7 +451,7 @@ void ddog_crasht_CrashInfoBuilder_drop(struct ddog_crasht_Handle_CrashInfoBuilde * which has not previously been dropped. */ DDOG_CHECK_RETURN -struct ddog_crasht_CrashInfo_NewResult ddog_crasht_CrashInfoBuilder_build(struct ddog_crasht_Handle_CrashInfoBuilder *builder); +ddog_crasht_CrashInfo_NewResult ddog_crasht_CrashInfoBuilder_build(struct ddog_crasht_Handle_CrashInfoBuilder *builder); /** * # Safety @@ -854,7 +854,7 @@ struct ddog_StringWrapperResult ddog_crasht_demangle(ddog_CharSlice name, * signal handler is dangerous, so we fork a sidecar to do the stuff we aren't * allowed to do in the handler. * - * See comments in [datadog-crashtracker/lib.rs] for a full architecture description. + * See comments in [libdd-crashtracker/lib.rs] for a full architecture description. * # Safety * No safety concerns */ @@ -868,7 +868,7 @@ DDOG_CHECK_RETURN struct ddog_VoidResult ddog_crasht_receiver_entry_point_stdin( * signal handler is dangerous, so we fork a sidecar to do the stuff we aren't * allowed to do in the handler. * - * See comments in [datadog-crashtracker/lib.rs] for a full architecture + * See comments in [libdd-crashtracker/lib.rs] for a full architecture * description. * # Safety * No safety concerns diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 4746e0d2163..1115054108b 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -92,6 +92,72 @@ void ddog_sidecar_transport_drop(struct ddog_SidecarTransport*); */ ddog_MaybeError ddog_sidecar_connect(struct ddog_SidecarTransport **connection); +/** + * Start master listener thread for thread-based connections (Unix only). + * + * This spawns a listener thread that accepts worker connections. Only one + * master listener can be active per process. + * + * # Arguments + * * `pid` - Process ID that owns this listener + * + * # Returns + * * `MaybeError::None` on success + * * `MaybeError::Some(Error)` on failure + */ +ddog_MaybeError ddog_sidecar_connect_master(int32_t pid); + +/** + * Connect as worker to master listener thread (Unix only). + * + * Establishes a connection to the master listener for the given PID. + * + * # Arguments + * * `pid` - Process ID of the master process + * * `connection` - Output parameter for the connection + * + * # Returns + * * `MaybeError::None` on success + * * `MaybeError::Some(Error)` on failure + */ +ddog_MaybeError ddog_sidecar_connect_worker(int32_t pid, struct ddog_SidecarTransport **connection); + +/** + * Shutdown the master listener thread (Unix only). + * + * Sends shutdown signal and joins the listener thread. This is blocking. + * + * # Returns + * * `MaybeError::None` on success + * * `MaybeError::Some(Error)` on failure + */ +ddog_MaybeError ddog_sidecar_shutdown_master_listener(void); + +/** + * Check if master listener is active for the given PID (Unix only). + * + * Used for fork detection. + * + * # Arguments + * * `pid` - Process ID to check + * + * # Returns + * * `true` if listener is active for this PID + * * `false` otherwise + */ +bool ddog_sidecar_is_master_listener_active(int32_t pid); + +/** + * Clear inherited master listener state in child after fork (Unix only). + * + * Child processes must call this to avoid using the parent's listener. + * + * # Returns + * * `MaybeError::None` on success + * * `MaybeError::Some(Error)` on failure + */ +ddog_MaybeError ddog_sidecar_clear_inherited_listener(void); + ddog_MaybeError ddog_sidecar_ping(struct ddog_SidecarTransport **transport); ddog_MaybeError ddog_sidecar_flush_traces(struct ddog_SidecarTransport **transport); @@ -118,7 +184,8 @@ ddog_MaybeError ddog_sidecar_telemetry_enqueueConfig(struct ddog_SidecarTranspor ddog_CharSlice config_key, ddog_CharSlice config_value, enum ddog_ConfigurationOrigin origin, - ddog_CharSlice config_id); + ddog_CharSlice config_id, + struct ddog_Option_U64 seq_id); /** * Reports a dependency to the telemetry. diff --git a/components-rs/telemetry.h b/components-rs/telemetry.h index fd8ffadcdbf..5a6c6288ece 100644 --- a/components-rs/telemetry.h +++ b/components-rs/telemetry.h @@ -42,7 +42,8 @@ ddog_MaybeError ddog_telemetry_builder_with_config(struct ddog_TelemetryWorkerBu ddog_CharSlice name, ddog_CharSlice value, enum ddog_ConfigurationOrigin origin, - ddog_CharSlice config_id); + ddog_CharSlice config_id, + struct ddog_Option_U64 seq_id); /** * Builds the telemetry worker and return a handle to it diff --git a/ext/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/configuration.c b/ext/configuration.c index 5e6e0bfca77..71fa4c0951a 100644 --- a/ext/configuration.c +++ b/ext/configuration.c @@ -96,6 +96,21 @@ static bool dd_parse_sampling_rules_format(zai_str value, zval *decoded_value, b return true; } +static bool dd_parse_sidecar_connection_mode(zai_str value, zval *decoded_value, bool persistent) { + UNUSED(persistent); + if (zai_str_eq_ci_cstr(value, "auto")) { + ZVAL_LONG(decoded_value, DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO); + } else if (zai_str_eq_ci_cstr(value, "subprocess")) { + ZVAL_LONG(decoded_value, DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS); + } else if (zai_str_eq_ci_cstr(value, "thread")) { + ZVAL_LONG(decoded_value, DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD); + } else { + return false; + } + + return true; +} + static bool dd_parse_tags(zai_str value, zval *decoded_value, bool persistent) { ZVAL_ARR(decoded_value, pemalloc(sizeof(HashTable), persistent)); zend_hash_init(Z_ARR_P(decoded_value), 8, NULL, persistent ? ZVAL_INTERNAL_PTR_DTOR : ZVAL_PTR_DTOR, persistent); diff --git a/ext/configuration.h b/ext/configuration.h index 362591df462..49e1a46f737 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -30,6 +30,12 @@ enum ddtrace_sampling_rules_format { DD_TRACE_SAMPLING_RULES_FORMAT_GLOB }; +enum ddtrace_sidecar_connection_mode { + DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO = 0, // Default: try subprocess, fallback to thread + DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS = 1, // Force subprocess only + DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD = 2, // Force thread only +}; + /* From the curl docs on CONNECT_TIMEOUT_MS: * If libcurl is built to use the standard system name resolver, that * portion of the transfer will still use full-second resolution for @@ -225,6 +231,7 @@ enum ddtrace_sampling_rules_format { CONFIG(STRING, DD_TRACE_AGENT_TEST_SESSION_TOKEN, "", .ini_change = ddtrace_alter_test_session_token) \ CONFIG(BOOL, DD_TRACE_PROPAGATE_USER_ID_DEFAULT, "false") \ CONFIG(CUSTOM(INT), DD_DBM_PROPAGATION_MODE, "disabled", .parser = dd_parse_dbm_mode) \ + CONFIG(CUSTOM(INT), DD_TRACE_SIDECAR_CONNECTION_MODE, "auto", .parser = dd_parse_sidecar_connection_mode) \ CONFIG(SET, DD_TRACE_WORDPRESS_ADDITIONAL_ACTIONS, "") \ CONFIG(BOOL, DD_TRACE_WORDPRESS_CALLBACKS, "true") \ CONFIG(BOOL, DD_INTEGRATION_METRICS_ENABLED, "true", \ diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 251da2bc3a6..cb7b6a5f4ff 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1529,6 +1529,7 @@ static PHP_MINIT_FUNCTION(ddtrace) { #endif ddshared_minit(); ddtrace_autoload_minit(); + ddtrace_sidecar_minit(); dd_register_span_data_ce(); dd_register_fatal_error_ce(); @@ -1585,7 +1586,9 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) { #ifndef _WIN32 ddtrace_signals_mshutdown(); - if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) { + // For CLI SAPI, background sender is already shut down in RSHUTDOWN + // For non-CLI SAPIs, shut it down here in MSHUTDOWN + if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER() && strcmp(sapi_module.name, "cli") != 0) { ddtrace_coms_mshutdown(); if (ddtrace_coms_flush_shutdown_writer_synchronous()) { ddtrace_coms_curl_shutdown(); @@ -1612,7 +1615,11 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) { ddtrace_user_req_shutdown(); - ddtrace_sidecar_shutdown(); + // Only shutdown sidecar in MSHUTDOWN for non-CLI SAPIs + // CLI SAPI shuts down in RSHUTDOWN to allow thread joins before ASAN checks + if (strcmp(sapi_module.name, "cli") != 0) { + ddtrace_sidecar_shutdown(); + } ddtrace_live_debugger_mshutdown(); @@ -2632,6 +2639,21 @@ void dd_internal_handle_fork(void) { ddtrace_coms_curl_shutdown(); ddtrace_coms_clean_background_sender_after_fork(); } + + // Handle thread mode after fork + int32_t current_pid = (int32_t)getpid(); + bool is_child_process = (ddtrace_sidecar_master_pid != 0 && + current_pid != ddtrace_sidecar_master_pid); + + if (is_child_process && ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + // Clear inherited master listener state (child doesn't own it) + ddtrace_ffi_try("Failed clearing inherited listener state", + ddog_sidecar_clear_inherited_listener()); + + // Don't try to reconnect in thread mode after fork + // Let sidecar stay unavailable + LOG(WARN, "Child process after fork with thread mode: sidecar unavailable"); + } #endif if (DDTRACE_G(agent_config_reader)) { ddog_agent_remote_config_reader_drop(DDTRACE_G(agent_config_reader)); diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 9c005f69d09..a782ea94b39 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -541,6 +541,9 @@ static void register_ddtrace_symbols(int module_number) REGISTER_LONG_CONSTANT("DDTrace\\DBM_PROPAGATION_DISABLED", DD_TRACE_DBM_PROPAGATION_DISABLED, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("DDTrace\\DBM_PROPAGATION_SERVICE", DD_TRACE_DBM_PROPAGATION_SERVICE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("DDTrace\\DBM_PROPAGATION_FULL", DD_TRACE_DBM_PROPAGATION_FULL, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("DDTrace\\SIDECAR_CONNECTION_MODE_AUTO", DD_TRACE_SIDECAR_CONNECTION_MODE_AUTO, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("DDTrace\\SIDECAR_CONNECTION_MODE_SUBPROCESS", DD_TRACE_SIDECAR_CONNECTION_MODE_SUBPROCESS, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("DDTrace\\SIDECAR_CONNECTION_MODE_THREAD", DD_TRACE_SIDECAR_CONNECTION_MODE_THREAD, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("DDTrace\\Internal\\SPAN_FLAG_OPENTELEMETRY", DDTRACE_SPAN_FLAG_OPENTELEMETRY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("DDTrace\\Internal\\SPAN_FLAG_OPENTRACING", DDTRACE_SPAN_FLAG_OPENTRACING, CONST_PERSISTENT); REGISTER_STRING_CONSTANT("DD_TRACE_VERSION", PHP_DDTRACE_VERSION, CONST_PERSISTENT); diff --git a/ext/handlers_pcntl.c b/ext/handlers_pcntl.c index acff140cfe4..22b5b31d32a 100644 --- a/ext/handlers_pcntl.c +++ b/ext/handlers_pcntl.c @@ -37,6 +37,15 @@ static void dd_prefork() { static void dd_handle_fork(zval *return_value) { if (Z_LVAL_P(return_value) == 0) { + // CHILD PROCESS + + // Warn if thread mode is active + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD) { + LOG(WARN, "pcntl_fork() detected with thread-based sidecar connection. " + "Thread mode is incompatible with fork and may cause instability. " + "Consider using subprocess mode (DD_TRACE_SIDECAR_CONNECTION_MODE=subprocess)."); + } + dd_internal_handle_fork(); } else { #if JOIN_BGS_BEFORE_FORK diff --git a/ext/sidecar.c b/ext/sidecar.c index 9732879acc5..26155f4b417 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" @@ -25,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())) { @@ -115,6 +121,10 @@ static void dd_sidecar_post_connect(ddog_SidecarTransport **transport, bool is_f } } +static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork); +static ddog_SidecarTransport *dd_sidecar_connection_factory_ex_non_fork(void); +static void ddtrace_sidecar_setup_master(bool appsec_activation, bool appsec_config); + static void dd_sidecar_on_reconnect(ddog_SidecarTransport *transport) { if (!ddtrace_endpoint || !dogstatsd_endpoint) { return; @@ -158,6 +168,41 @@ 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 *dd_sidecar_connection_factory_ex(bool is_fork) { // Should not happen, unless the agent url is malformed if (!ddtrace_endpoint) { @@ -178,20 +223,183 @@ static ddog_SidecarTransport *dd_sidecar_connection_factory_ex(bool is_fork) { } 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))) { + 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; } -ddog_SidecarTransport *dd_sidecar_connection_factory(void) { +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 + 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 (!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))) { + 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_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; + } + + // 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]; + 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_ex_non_fork); + + if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { + ddtrace_telemetry_first_init(); + } + + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_THREAD; +} + +ddog_SidecarTransport *ddtrace_sidecar_connect_thread(void) { + return dd_sidecar_connection_factory_ex(false); +} + +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 + 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 + 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, fallback to thread if needed + 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 { + // 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; + } + + return transport; +} + +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) { *appsec_activation = false; *appsec_config = false; @@ -217,26 +425,52 @@ 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 = dd_sidecar_connection_factory(); - 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(); } + } else if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { + ddtrace_telemetry_first_init(); } +} - if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { - ddtrace_telemetry_first_init(); +void ddtrace_sidecar_minit(void) { +#ifndef _WIN32 + 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 } 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); } } @@ -261,8 +495,29 @@ void ddtrace_sidecar_finalize(bool clear_id) { } void ddtrace_sidecar_shutdown(void) { +#ifndef _WIN32 + // Shutdown master listener if this is the master process and thread mode is active + int32_t current_pid = (int32_t)getpid(); + if (ddtrace_sidecar_active_mode == DD_SIDECAR_CONNECTION_THREAD && + ddtrace_sidecar_master_pid != 0 && + current_pid == ddtrace_sidecar_master_pid) { + + // Close worker connection first to avoid deadlock + if (ddtrace_sidecar) { + ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; + } + + // Then shutdown listener thread + ddtrace_ffi_try("Failed shutting down master listener", + ddog_sidecar_shutdown_master_listener()); + } +#endif + + // Standard cleanup if (ddtrace_sidecar_instance_id) { ddog_sidecar_instanceId_drop(ddtrace_sidecar_instance_id); + ddtrace_sidecar_instance_id = NULL; } if (ddtrace_endpoint) { @@ -271,7 +526,11 @@ void ddtrace_sidecar_shutdown(void) { if (ddtrace_sidecar) { ddog_sidecar_transport_drop(ddtrace_sidecar); + ddtrace_sidecar = NULL; } + + // Reset mode + ddtrace_sidecar_active_mode = DD_SIDECAR_CONNECTION_NONE; } void ddtrace_force_new_instance_id(void) { @@ -286,8 +545,10 @@ void ddtrace_reset_sidecar(void) { if (ddtrace_sidecar) { ddog_sidecar_transport_drop(ddtrace_sidecar); - ddtrace_sidecar = dd_sidecar_connection_factory_ex(true); - if (!ddtrace_sidecar) { // Something went wrong + ddtrace_sidecar = NULL; + + ddtrace_sidecar = ddtrace_sidecar_connect(true); + if (!ddtrace_sidecar) { if (ddtrace_endpoint) { dd_free_endpoints(); } @@ -596,6 +857,28 @@ 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 b3593ceaf1c..26b01b7c0ea 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(bool is_fork); + +// 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); diff --git a/libdatadog b/libdatadog index 1caf15157a8..d87edf1022e 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 1caf15157a8251d398715349a75d5607cb5545c2 +Subproject commit d87edf1022e41e292841f886627319fd950a66fb 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/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/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php b/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php new file mode 100644 index 00000000000..3cf39a6908a --- /dev/null +++ b/tests/Integrations/Custom/Autoloaded/SidecarThreadModeTest.php @@ -0,0 +1,91 @@ + 'sidecar-thread-mode-test', + // Explicitly force thread mode to test the thread implementation + 'DD_TRACE_SIDECAR_CONNECTION_MODE' => 'thread', + 'DD_TRACE_DEBUG' => '0', + ]); + } + + public function testThreadModeTracesAreSubmitted() + { + if (\getenv('DD_TRACE_TEST_SAPI') !== 'fpm-fcgi') { + $this->markTestSkipped('This test requires DD_TRACE_TEST_SAPI=fpm-fcgi'); + } + + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Thread mode not supported on Windows'); + } + + // This test validates that when thread mode is explicitly configured, + // traces are successfully submitted through the thread-based sidecar + $traces = $this->tracesFromWebRequest(function () { + $spec = GetSpec::create('Thread mode trace', '/simple?key=value'); + $this->call($spec); + }); + + // Verify traces were submitted + $this->assertNotEmpty($traces, 'Expected traces to be submitted through thread-based sidecar'); + $this->assertCount(1, $traces[0], 'Expected one span in the trace'); + + $span = $traces[0][0]; + $this->assertSame('web.request', $span['name']); + $this->assertSame('sidecar-thread-mode-test', $span['service']); + } + + public function testThreadModeMultipleRequests() + { + if (\getenv('DD_TRACE_TEST_SAPI') !== 'fpm-fcgi') { + $this->markTestSkipped('This test requires DD_TRACE_TEST_SAPI=fpm-fcgi'); + } + + if (PHP_OS_FAMILY === 'Windows') { + $this->markTestSkipped('Thread mode not supported on Windows'); + } + + // This test validates that multiple PHP-FPM workers can successfully + // connect to the same master listener thread and submit traces + $traces = $this->tracesFromWebRequest(function () { + for ($i = 0; $i < 3; $i++) { + $spec = GetSpec::create("Request $i", "/simple?request=$i"); + $this->call($spec); + } + }); + + // Verify all traces were submitted + $this->assertGreaterThanOrEqual(3, count($traces), 'Expected at least 3 traces from multiple requests'); + + foreach ($traces as $trace) { + $this->assertNotEmpty($trace); + $this->assertSame('web.request', $trace[0]['name']); + } + } +} diff --git a/tests/Sapi/PhpFpm/PhpFpm.php b/tests/Sapi/PhpFpm/PhpFpm.php index 3c93541ddb4..bfe5035497f 100644 --- a/tests/Sapi/PhpFpm/PhpFpm.php +++ b/tests/Sapi/PhpFpm/PhpFpm.php @@ -34,23 +34,31 @@ final class PhpFpm implements Sapi */ private $logFile; + /** + * @var int + */ + private $maxChildren; + /** * @param string $rootPath * @param string $host * @param int $port * @param array $envs * @param array $inis + * @param int $maxChildren */ - public function __construct($rootPath, $host, $port, array $envs = [], array $inis = []) + public function __construct($rootPath, $host, $port, array $envs = [], array $inis = [], $maxChildren = 1) { $this->envs = $envs; $this->inis = $inis; + $this->maxChildren = $maxChildren; $logPath = $rootPath . '/' . self::ERROR_LOG; $replacements = [ '{{fcgi_host}}' => $host, '{{fcgi_port}}' => $port, + '{{max_children}}' => $maxChildren, '{{envs}}' => $this->envsForConfFile(), '{{inis}}' => $this->inisForConfFile(), '{{error_log}}' => $logPath, @@ -76,10 +84,17 @@ public function __construct($rootPath, $host, $port, array $envs = [], array $in public function start() { + $allowRoot = ''; + // Check if running as root and add --allow-to-run-as-root flag + if (function_exists('posix_getuid') && posix_getuid() === 0) { + $allowRoot = ' --allow-to-run-as-root'; + } + $cmd = sprintf( - 'php-fpm -p %s --fpm-config %s -F', + 'php-fpm -p %s --fpm-config %s -F%s', __DIR__, - $this->configFile + $this->configFile, + $allowRoot ); $processCmd = "exec $cmd"; diff --git a/tests/Sapi/PhpFpm/www.conf b/tests/Sapi/PhpFpm/www.conf index 6dd5da6df13..a8552244b9b 100644 --- a/tests/Sapi/PhpFpm/www.conf +++ b/tests/Sapi/PhpFpm/www.conf @@ -5,7 +5,7 @@ error_log = {{error_log}} listen = {{fcgi_host}}:{{fcgi_port}} pm = static -pm.max_children = 1 +pm.max_children = {{max_children}} pm.status_path = /status catch_workers_output = yes diff --git a/tests/WebServer.php b/tests/WebServer.php index 18c4679635c..c40be2da531 100644 --- a/tests/WebServer.php +++ b/tests/WebServer.php @@ -80,6 +80,7 @@ final class WebServer private $isOctane = false; private $isFrankenphp = false; private $isSwoole = false; + private $phpFpmMaxChildren = 1; private $defaultInis = [ 'log_errors' => 'on', @@ -132,6 +133,11 @@ public function setFrankenphp() $this->isFrankenphp = true; } + public function setPhpFpmMaxChildren($maxChildren) + { + $this->phpFpmMaxChildren = $maxChildren; + } + public function start() { if (!isset($this->envs['DD_TRACE_DEBUG'])) { @@ -192,7 +198,8 @@ public function start() self::FCGI_HOST, self::FCGI_PORT, $this->envs, - $this->inis + $this->inis, + $this->phpFpmMaxChildren ); break; case 'apache2handler': diff --git a/tests/ext/sidecar_connection_mode_auto.phpt b/tests/ext/sidecar_connection_mode_auto.phpt new file mode 100644 index 00000000000..449899f8d29 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_auto.phpt @@ -0,0 +1,24 @@ +--TEST-- +Sidecar connection in auto mode (default) +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=auto +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECT-- +Connection mode: auto +Span created successfully diff --git a/tests/ext/sidecar_connection_mode_config.phpt b/tests/ext/sidecar_connection_mode_config.phpt new file mode 100644 index 00000000000..b7ae94580fd --- /dev/null +++ b/tests/ext/sidecar_connection_mode_config.phpt @@ -0,0 +1,16 @@ +--TEST-- +DD_TRACE_SIDECAR_CONNECTION_MODE configuration parsing +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=subprocess +--FILE-- + +--EXPECT-- +Test 1: subprocess mode +string(10) "subprocess" diff --git a/tests/ext/sidecar_connection_mode_fork_warning.phpt b/tests/ext/sidecar_connection_mode_fork_warning.phpt new file mode 100644 index 00000000000..0a136b12cb8 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_fork_warning.phpt @@ -0,0 +1,38 @@ +--TEST-- +Fork with thread mode configuration (thread mode not active in CLI) +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=thread +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECTF-- +Connection mode: thread +Child process +Parent process diff --git a/tests/ext/sidecar_connection_mode_invalid.phpt b/tests/ext/sidecar_connection_mode_invalid.phpt new file mode 100644 index 00000000000..69db7138f94 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_invalid.phpt @@ -0,0 +1,24 @@ +--TEST-- +Invalid sidecar connection mode falls back to auto +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=invalid_mode +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECT-- +Connection mode: auto +Span created successfully diff --git a/tests/ext/sidecar_connection_mode_subprocess.phpt b/tests/ext/sidecar_connection_mode_subprocess.phpt new file mode 100644 index 00000000000..fb10c7a87c5 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_subprocess.phpt @@ -0,0 +1,24 @@ +--TEST-- +Sidecar connection in subprocess mode +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=subprocess +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECT-- +Connection mode: subprocess +Span created successfully diff --git a/tests/ext/sidecar_connection_mode_thread.phpt b/tests/ext/sidecar_connection_mode_thread.phpt new file mode 100644 index 00000000000..eb76ef9f175 --- /dev/null +++ b/tests/ext/sidecar_connection_mode_thread.phpt @@ -0,0 +1,31 @@ +--TEST-- +Sidecar connection in thread mode +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=thread +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +--FILE-- + +--EXPECTF-- +Connection mode: thread +Test completed diff --git a/tests/ext/sidecar_connection_mode_thread_functional.phpt b/tests/ext/sidecar_connection_mode_thread_functional.phpt new file mode 100644 index 00000000000..bbd7563e8be --- /dev/null +++ b/tests/ext/sidecar_connection_mode_thread_functional.phpt @@ -0,0 +1,98 @@ +--TEST-- +Thread mode sidecar connection sends traces (CLI with fallback to subprocess) +--SKIPIF-- + + + +--ENV-- +DD_TRACE_LOG_LEVEL=info,startup=off +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +DD_TRACE_SIDECAR_CONNECTION_MODE=auto +--INI-- +datadog.trace.agent_test_session_token=sidecar_thread_functional_test +--FILE-- +replayRequest(); + +echo "Connection mode: " . ini_get('datadog.trace.sidecar_connection_mode') . "\n"; + +// Create a span that should be sent via sidecar +DDTrace\start_span(); +DDTrace\active_span()->name = 'thread.mode.test'; +DDTrace\active_span()->service = 'thread-mode-functional-test'; +DDTrace\active_span()->resource = 'test_resource'; +DDTrace\active_span()->meta['test.key'] = 'test.value'; +DDTrace\close_span(); + +echo "Span created\n"; + +// Wait for trace to be sent and retrieve it +$trace_data = $rr->waitForDataAndReplay(); + +echo "Trace received\n"; + +// Parse the trace +$decoded = json_decode($trace_data["body"], true); + +// Handle both chunked and non-chunked formats +$spans = $decoded["chunks"][0]["spans"] ?? $decoded[0]; + +if (!is_array($spans) || empty($spans)) { + die("FAIL: No spans in trace\n"); +} + +$root_span = $spans[0]; + +// Verify span properties +if ($root_span["name"] !== "thread.mode.test") { + die("FAIL: Expected span name 'thread.mode.test', got: " . $root_span["name"] . "\n"); +} + +if ($root_span["service"] !== "thread-mode-functional-test") { + die("FAIL: Expected service 'thread-mode-functional-test', got: " . $root_span["service"] . "\n"); +} + +if ($root_span["resource"] !== "test_resource") { + die("FAIL: Expected resource 'test_resource', got: " . $root_span["resource"] . "\n"); +} + +if (!isset($root_span["meta"]["test.key"]) || $root_span["meta"]["test.key"] !== "test.value") { + die("FAIL: Expected meta tag 'test.key' = 'test.value'\n"); +} + +echo "Span properties verified\n"; +echo "Test passed: Sidecar connection works and traces are sent\n"; + +?> +--EXPECTF-- +Connection mode: auto +%a +Span created +Trace received +Span properties verified +Test passed: Sidecar connection works and traces are sent +%a diff --git a/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt b/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt new file mode 100644 index 00000000000..99c13eb961f --- /dev/null +++ b/tests/ext/sidecar_connection_mode_thread_phpfpm.phpt @@ -0,0 +1,243 @@ +--TEST-- +Thread mode connection with PHP-FPM (manual verification test) +--SKIPIF-- + +--ENV-- +DD_TRACE_SIDECAR_CONNECTION_MODE=thread +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +DD_TRACE_SIDECAR_TRACE_SENDER=1 +DD_TRACE_DEBUG=1 +--FILE-- +name = 'phpfpm.request'; +DDTrace\active_span()->service = 'thread-mode-test'; +DDTrace\active_span()->resource = 'GET /test'; +DDTrace\close_span(); + +echo "Request processed with thread mode\n"; +echo "Connection mode: " . ini_get('datadog.trace.sidecar_connection_mode') . "\n"; +PHP +); + +// Create PHP-FPM configuration +$fpmConfig = $tmpDir . '/php-fpm.conf'; +$fpmSocket = $tmpDir . '/php-fpm.sock'; +$fpmLog = $tmpDir . '/php-fpm.log'; + +file_put_contents($fpmConfig, << ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], +]; + +$fpmCmd = "php-fpm --fpm-config $fpmConfig --nodaemonize"; +$fpmProc = proc_open($fpmCmd, $descriptors, $pipes); + +if (!is_resource($fpmProc)) { + die("FAIL: Could not start PHP-FPM\n"); +} + +// Give PHP-FPM time to start and initialize thread mode +echo "Waiting for PHP-FPM to start...\n"; +$timeout = 5; +$start = time(); +while (!file_exists($fpmSocket) && (time() - $start) < $timeout) { + usleep(100000); // 100ms +} + +if (!file_exists($fpmSocket)) { + proc_terminate($fpmProc); + die("FAIL: PHP-FPM socket not created within timeout\n"); +} + +echo "PHP-FPM started successfully!\n\n"; + +// Make a FastCGI request to PHP-FPM +echo "Making FastCGI request...\n"; + +// Simple FastCGI client implementation for testing +$sock = stream_socket_client("unix://$fpmSocket", $errno, $errstr, 5); +if (!$sock) { + proc_terminate($fpmProc); + die("FAIL: Could not connect to PHP-FPM socket: $errstr\n"); +} + +// Build FastCGI request +$params = [ + 'GATEWAY_INTERFACE' => 'FastCGI/1.0', + 'REQUEST_METHOD' => 'GET', + 'SCRIPT_FILENAME' => $scriptPath, + 'SCRIPT_NAME' => '/index.php', + 'REQUEST_URI' => '/test', + 'DOCUMENT_ROOT' => $tmpDir, + 'SERVER_SOFTWARE' => 'php/fcgi', + 'REMOTE_ADDR' => '127.0.0.1', + 'REMOTE_PORT' => '9000', + 'SERVER_ADDR' => '127.0.0.1', + 'SERVER_PORT' => '80', + 'SERVER_NAME' => 'localhost', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'CONTENT_TYPE' => '', + 'CONTENT_LENGTH' => '0', +]; + +// Simplified FastCGI protocol implementation +function fcgi_begin_request($sock, $requestId, $role = 1, $flags = 0) { + $content = pack('nCx5', $role, $flags); + fcgi_write_record($sock, 1, $requestId, $content); // Type 1 = BEGIN_REQUEST +} + +function fcgi_write_params($sock, $requestId, $params) { + $content = ''; + foreach ($params as $key => $value) { + $keyLen = strlen($key); + $valLen = strlen($value); + + $content .= chr($keyLen); + $content .= chr($valLen); + $content .= $key . $value; + } + fcgi_write_record($sock, 4, $requestId, $content); // Type 4 = PARAMS + fcgi_write_record($sock, 4, $requestId, ''); // Empty PARAMS to signal end +} + +function fcgi_write_record($sock, $type, $requestId, $content) { + $clen = strlen($content); + $header = pack('CCnnxx', 1, $type, $requestId, $clen); + fwrite($sock, $header . $content); +} + +// Send FastCGI request +$requestId = 1; +fcgi_begin_request($sock, $requestId); +fcgi_write_params($sock, $requestId, $params); +fcgi_write_record($sock, 5, $requestId, ''); // Type 5 = STDIN (empty) + +// Read response +$response = ''; +$timeout = 5; +$start = time(); +stream_set_timeout($sock, 1); + +while (!feof($sock) && (time() - $start) < $timeout) { + $chunk = fread($sock, 8192); + if ($chunk === false) break; + $response .= $chunk; + if (strpos($response, "Request processed with thread mode") !== false) { + break; + } +} + +fclose($sock); + +// Parse response (simplified - just look for our output) +if (strpos($response, "Request processed with thread mode") !== false) { + echo "SUCCESS: Request processed through thread mode!\n"; + if (strpos($response, "Connection mode: thread") !== false) { + echo "SUCCESS: Thread mode configuration verified!\n"; + } +} else { + echo "FAIL: Did not receive expected response\n"; + echo "Response snippet: " . substr($response, 0, 200) . "\n"; +} + +echo "\n=== Test Complete ===\n"; +echo "Note: This test verifies that:\n"; +echo "1. PHP-FPM master process can start with thread mode\n"; +echo "2. PHP-FPM workers can process requests\n"; +echo "3. Thread mode configuration is active\n"; +echo "\nFor full verification of trace submission, check DD_TRACE_DEBUG logs\n"; +echo "showing master listener thread startup and worker connections.\n"; + +// Cleanup +echo "\nCleaning up...\n"; +proc_terminate($fpmProc); +proc_close($fpmProc); +unlink($scriptPath); +unlink($fpmConfig); +unlink($fpmSocket); +rmdir($tmpDir); + +?> +--EXPECTF-- +=== PHP-FPM Thread Mode Test === + +Test directory: %s +Socket: %s + +Starting PHP-FPM with thread mode... +Waiting for PHP-FPM to start... +PHP-FPM started successfully! + +Making FastCGI request... +SUCCESS: Request processed through thread mode! +SUCCESS: Thread mode configuration verified! + +=== Test Complete === +Note: This test verifies that: +1. PHP-FPM master process can start with thread mode +2. PHP-FPM workers can process requests +3. Thread mode configuration is active + +For full verification of trace submission, check DD_TRACE_DEBUG logs +showing master listener thread startup and worker connections. + +Cleaning up...