From 31fb92efe057fb8966e400dfda5893e320cd4541 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sat, 19 Sep 2026 08:37:01 -0600 Subject: [PATCH 1/3] fix: shutdown crash in remote config --- ext/datadog.c | 2 + ext/remote_config.c | 12 ++-- ext/sidecar.c | 4 +- .../dynamic_config_late_shutdown.phpt | 70 +++++++++++++++++++ 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 tests/ext/remote_config/dynamic_config_late_shutdown.phpt diff --git a/ext/datadog.c b/ext/datadog.c index b0bb80c531f..434161b673a 100644 --- a/ext/datadog.c +++ b/ext/datadog.c @@ -666,6 +666,8 @@ static PHP_RSHUTDOWN_FUNCTION(datadog) { datadog_sidecar_finalize(true); DATADOG_G(request_initialized) = false; + /* A signal may have queued a Remote Config reread during RSHUTDOWN. */ + DATADOG_G(reread_remote_configuration) = 0; datadog_telemetry_rshutdown(); datadog_sidecar_rshutdown(); diff --git a/ext/remote_config.c b/ext/remote_config.c index 81aabbc7f2e..f7dc913fca4 100644 --- a/ext/remote_config.c +++ b/ext/remote_config.c @@ -21,10 +21,12 @@ static void dd_vm_interrupt(zend_execute_data *execute_data) { if (dd_prev_interrupt_function) { dd_prev_interrupt_function(execute_data); } - if (DATADOG_G(remote_config_state) && DATADOG_G(reread_remote_configuration)) { - LOG(INFO, "Rereading remote configurations after interrupt"); + if (DATADOG_G(reread_remote_configuration)) { DATADOG_G(reread_remote_configuration) = 0; - ddog_process_remote_configs(DATADOG_G(remote_config_state)); + if (DATADOG_G(request_initialized) && DATADOG_G(remote_config_state)) { + LOG(INFO, "Rereading remote configurations after interrupt"); + ddog_process_remote_configs(DATADOG_G(remote_config_state)); + } } } @@ -53,7 +55,9 @@ DATADOG_PUBLIC void datadog_set_all_thread_vm_interrupt(void) { } void datadog_check_for_new_config_now(void) { - if (DATADOG_G(remote_config_state) && !DATADOG_G(reread_remote_configuration) && ddog_process_remote_configs(DATADOG_G(remote_config_state))) { + if (DATADOG_G(request_initialized) && DATADOG_G(remote_config_state) && + !DATADOG_G(reread_remote_configuration) && + ddog_process_remote_configs(DATADOG_G(remote_config_state))) { // If we blocked the signal, notify the other threads too datadog_set_all_thread_vm_interrupt(); } diff --git a/ext/sidecar.c b/ext/sidecar.c index ca9706133fd..e5439e451cd 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -815,7 +815,7 @@ void ddtrace_sidecar_submit_span_data_direct(ddog_SidecarTransport **transport, const ddog_Vec_Tag *process_tags = datadog_process_tags_get_vec(); bool changed = true; - if (DATADOG_G(remote_config_state)) { + if (DATADOG_G(request_initialized) && DATADOG_G(remote_config_state)) { changed = ddog_remote_configs_service_env_change(DATADOG_G(remote_config_state), service_slice, env_slice, version_slice, &DATADOG_G(active_global_tags), process_tags); } @@ -853,7 +853,7 @@ void ddtrace_sidecar_submit_span_data_direct(ddog_SidecarTransport **transport, ddog_sidecar_telemetry_filter_flush(transport, datadog_sidecar_instance_id, &DATADOG_G(sidecar_queue_id), datadog_telemetry_buffer(), datadog_telemetry_cache(), service_slice, env_slice)); } - if (DATADOG_G(remote_config_state)) { + if (DATADOG_G(request_initialized) && DATADOG_G(remote_config_state)) { // Must happen after ddog_sidecar_set_universal_service_tags (session state fully initialized) ddog_process_remote_configs(DATADOG_G(remote_config_state)); } diff --git a/tests/ext/remote_config/dynamic_config_late_shutdown.phpt b/tests/ext/remote_config/dynamic_config_late_shutdown.phpt new file mode 100644 index 00000000000..24ed9fb8851 --- /dev/null +++ b/tests/ext/remote_config/dynamic_config_late_shutdown.phpt @@ -0,0 +1,70 @@ +--TEST-- +Remote config is not reapplied after its request shutdown cleanup +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.1 +DD_TRACE_AGENT_TEST_SESSION_TOKEN=remote-config/dynamic_config_late_shutdown +--FILE-- + true, +]); + +\DDTrace\start_span(); +if (ini_get('datadog.logs_injection') !== '1') { + dd_trace_internal_fn('await_remote_config'); +} +var_dump(ini_get('datadog.logs_injection')); + +stream_wrapper_register('late-remote-config', LateRemoteConfigWrapper::class); +$trigger = fopen('late-remote-config://trigger', 'r'); + +echo "request body complete\n"; + +?> +--CLEAN-- + +--EXPECT-- +string(1) "1" +request body complete +late close completed From 67f7c73f18347ba9a58af456e1282752f7981ac0 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sat, 19 Sep 2026 11:00:59 -0600 Subject: [PATCH 2/3] fix: live debugger prshutdown --- ext/datadog.c | 7 +-- .../debugger_rshutdown_remote_config.phpt | 60 +++++++++++++++++++ .../dynamic_config_late_shutdown.phpt | 12 +--- tracer/ddtrace.c | 2 +- tracer/functions.c | 4 ++ 5 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 tests/ext/live-debugger/debugger_rshutdown_remote_config.phpt diff --git a/ext/datadog.c b/ext/datadog.c index 434161b673a..4de884d3513 100644 --- a/ext/datadog.c +++ b/ext/datadog.c @@ -652,10 +652,6 @@ static PHP_RSHUTDOWN_FUNCTION(datadog) { bool fast_shutdown = is_zend_mm() && !EG(full_tables_cleanup); #endif - if (DATADOG_G(remote_config_state)) { - datadog_rshutdown_remote_config(); - } - if (!datadog_disable) { dd_shutdown_observer(); } @@ -668,6 +664,9 @@ static PHP_RSHUTDOWN_FUNCTION(datadog) { DATADOG_G(request_initialized) = false; /* A signal may have queued a Remote Config reread during RSHUTDOWN. */ DATADOG_G(reread_remote_configuration) = 0; + if (DATADOG_G(remote_config_state)) { + datadog_rshutdown_remote_config(); + } datadog_telemetry_rshutdown(); datadog_sidecar_rshutdown(); diff --git a/tests/ext/live-debugger/debugger_rshutdown_remote_config.phpt b/tests/ext/live-debugger/debugger_rshutdown_remote_config.phpt new file mode 100644 index 00000000000..5e48e7a306f --- /dev/null +++ b/tests/ext/live-debugger/debugger_rshutdown_remote_config.phpt @@ -0,0 +1,60 @@ +--TEST-- +Remote Config and live debugger hooks remain valid through tracer request shutdown +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.1 +DD_TRACE_AGENT_TEST_SESSION_TOKEN=live-debugger/rshutdown_remote_config +--INI-- +datadog.trace.enabled=1 +datadog.autofinish_spans=1 +--FILE-- +onClose[] = function () { + // The span is closed from tracer RSHUTDOWN. Remote Config and its + // live debugger subscriber must still be active. + var_dump(count(dd_trace_internal_fn('get_loaded_remote_configs'))); + echo "onClose completed\n"; + }; + } +} + +put_dynamic_config_file([ + "log_injection_enabled" => true, + "dynamic_instrumentation_enabled" => true, +]); + +await_probe_installation(function () { + build_span_probe(["where" => ["methodName" => "instrumented"]]); +}); + +$trigger = new StartSpanDuringShutdown(); + +echo "request body complete\n"; + +?> +--CLEAN-- + +--EXPECT-- +request body complete +int(2) +onClose completed diff --git a/tests/ext/remote_config/dynamic_config_late_shutdown.phpt b/tests/ext/remote_config/dynamic_config_late_shutdown.phpt index 24ed9fb8851..19888f566af 100644 --- a/tests/ext/remote_config/dynamic_config_late_shutdown.phpt +++ b/tests/ext/remote_config/dynamic_config_late_shutdown.phpt @@ -3,9 +3,6 @@ Remote config is not reapplied after its request shutdown cleanup --SKIPIF-- --ENV-- DD_AGENT_HOST=request-replayer @@ -30,12 +27,9 @@ final class LateRemoteConfigWrapper public function stream_close(): void { - // Resource destruction runs after module RSHUTDOWN. Make the pending - // Remote Config interrupt observable to the VM in that interval. - posix_kill(posix_getpid(), SIGVTALRM); - for ($i = 0; $i < 10; $i++) { - // execute opcodes until the VM services the interrupt - } + // Resource destruction runs after module RSHUTDOWN. Process Remote + // Config synchronously so this lifecycle boundary is deterministic. + dd_trace_internal_fn('process_remote_config'); echo "late close completed\n"; } } diff --git a/tracer/ddtrace.c b/tracer/ddtrace.c index df9b73ef12c..65f0434b255 100644 --- a/tracer/ddtrace.c +++ b/tracer/ddtrace.c @@ -628,7 +628,6 @@ void ddtrace_rshutdown(bool fast_shutdown) { ddtrace_clean_git_object(); ddtrace_weak_resources_rshutdown(); - ddtrace_live_debugger_rshutdown(); } void ddtrace_post_deactivate(void) { @@ -637,6 +636,7 @@ void ddtrace_post_deactivate(void) { zai_interceptor_deactivate(); // we can only actually free our hooks hashtables in post_deactivate, as within RSHUTDOWN some user code may still run + ddtrace_live_debugger_rshutdown(); zai_hook_rshutdown(); zai_uhook_rshutdown(); } diff --git a/tracer/functions.c b/tracer/functions.c index b0744e569b8..34bd35b302e 100644 --- a/tracer/functions.c +++ b/tracer/functions.c @@ -2215,6 +2215,10 @@ PHP_FUNCTION(dd_trace_internal_fn) { } else { array_init(return_value); } + } else if (FUNCTION_NAME_MATCHES("process_remote_config")) { + // Test/debug helper for exercising Remote Config at precise lifecycle points. + datadog_check_for_new_config_now(); + RETVAL_TRUE; } else if (FUNCTION_NAME_MATCHES("await_remote_config")) { uint32_t timeout_sec = 10; if (params_count == 1) { From 91d98e7bd4664011a34a77baadec83ecda95377f Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Mon, 21 Sep 2026 13:32:25 +0200 Subject: [PATCH 3/3] Update dynamic_config_late_shutdown.phpt Skip types for PHP 7.0 --- tests/ext/remote_config/dynamic_config_late_shutdown.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ext/remote_config/dynamic_config_late_shutdown.phpt b/tests/ext/remote_config/dynamic_config_late_shutdown.phpt index 19888f566af..2be7889233b 100644 --- a/tests/ext/remote_config/dynamic_config_late_shutdown.phpt +++ b/tests/ext/remote_config/dynamic_config_late_shutdown.phpt @@ -20,12 +20,12 @@ final class LateRemoteConfigWrapper { public $context; - public function stream_open($path, $mode, $options, &$opened_path): bool + public function stream_open($path, $mode, $options, &$opened_path) { return true; } - public function stream_close(): void + public function stream_close() { // Resource destruction runs after module RSHUTDOWN. Process Remote // Config synchronously so this lifecycle boundary is deterministic.