From 0ca6b04fb6dbc54aca2f5b4afd97cb2589b6b29b Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Wed, 17 Jun 2026 12:08:43 +0200 Subject: [PATCH 01/11] feat: add DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT config Adds the new `DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT` config key (values: continue, restart, ignore) with a CUSTOM(INT) parser, the corresponding C enum, and the supported-configurations.json entry. Co-Authored-By: Claude Sonnet 4.6 --- metadata/supported-configurations.json | 7 +++++++ tracer/configuration.h | 7 +++++++ tracer/configuration_dependencies.h | 15 +++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 1cf6ad217f..d1c93f72a8 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -1995,6 +1995,13 @@ "default": "false" } ], + "DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT": [ + { + "implementation": "A", + "type": "string", + "default": "continue" + } + ], "DD_TRACE_PROPAGATION_STYLE": [ { "implementation": "D", diff --git a/tracer/configuration.h b/tracer/configuration.h index 45790f6a64..ab888552af 100644 --- a/tracer/configuration.h +++ b/tracer/configuration.h @@ -99,6 +99,7 @@ CONFIG(SET_LOWERCASE, DD_TRACE_PROPAGATION_STYLE, "datadog,tracecontext,baggage", \ .env_config_fallback = ddtrace_conf_otel_propagators) \ CONFIG(SET, DD_TRACE_BAGGAGE_TAG_KEYS, "user.id, session.id, account.id") \ + CONFIG(CUSTOM(INT), DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT, "continue", .parser = dd_parse_propagation_behavior_extract) \ CONFIG(BOOL, DD_TRACE_IGNORE_AGENT_SAMPLING_RATES, "false", .ini_change = zai_config_system_ini_change) \ CONFIG(SET, DD_TRACE_TRACED_INTERNAL_FUNCTIONS, "") \ CONFIG(INT, DD_TRACE_DEBUG_PRNG_SEED, "-1", .ini_change = ddtrace_reseed_seed_change) \ @@ -192,6 +193,12 @@ enum ddtrace_sampling_rules_format { DD_TRACE_SAMPLING_RULES_FORMAT_GLOB }; +enum ddtrace_propagation_behavior_extract { + DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE = 0, + DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART, + DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE, +}; + #define DD_CONFIGURATION DDTRACE_CONFIGURATION #include diff --git a/tracer/configuration_dependencies.h b/tracer/configuration_dependencies.h index 70ad64dc08..c47a31ea74 100644 --- a/tracer/configuration_dependencies.h +++ b/tracer/configuration_dependencies.h @@ -22,6 +22,21 @@ static bool dd_parse_dbm_mode(zai_str value, zval *decoded_value, bool persisten return true; } +static bool dd_parse_propagation_behavior_extract(zai_str value, zval *decoded_value, bool persistent) { + UNUSED(persistent); + if (zai_str_eq_ci_cstr(value, "continue")) { + ZVAL_LONG(decoded_value, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE); + } else if (zai_str_eq_ci_cstr(value, "restart")) { + ZVAL_LONG(decoded_value, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART); + } else if (zai_str_eq_ci_cstr(value, "ignore")) { + ZVAL_LONG(decoded_value, DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE); + } else { + return false; + } + + return true; +} + static bool dd_parse_sampling_rules_format(zai_str value, zval *decoded_value, bool persistent) { UNUSED(persistent); if (zai_str_eq_ci_cstr(value, "regex")) { From 5a635786b18ad49f66b1051336c7e034439c083a Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Wed, 17 Jun 2026 12:08:53 +0200 Subject: [PATCH 02/11] feat: implement DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT behavior Adds three behaviors at the single extraction chokepoint (ddtrace_apply_distributed_tracing_result): - continue (default): inherit upstream trace context unchanged - restart: start a fresh trace; upstream captured as a span link with reason=propagation_behavior_extract. When the root span doesn't exist yet at request-init time, the link is queued in DDTRACE_G(pending_upstream_span_link) and attached in ddtrace_open_span. - ignore: drop all extracted context including baggage and sampling priority Also extracts ddtrace_build_span_link_from_result() from the SpanLink fromHeaders method so it can be reused by the restart path. Co-Authored-By: Claude Sonnet 4.6 --- tracer/ddtrace.c | 3 ++ tracer/ddtrace_globals.h | 1 + tracer/distributed_tracing_headers.c | 53 ++++++++++++++++++++++++++++ tracer/distributed_tracing_headers.h | 2 ++ tracer/functions.c | 30 +++++++++------- tracer/span.c | 7 ++++ 6 files changed, 83 insertions(+), 13 deletions(-) diff --git a/tracer/ddtrace.c b/tracer/ddtrace.c index 7f5e9681d9..60181d437f 100644 --- a/tracer/ddtrace.c +++ b/tracer/ddtrace.c @@ -436,6 +436,7 @@ static void dd_initialize_request(void) { DDTRACE_G(default_priority_sampling) = DDTRACE_PRIORITY_SAMPLING_UNKNOWN; DDTRACE_G(propagated_priority_sampling) = DDTRACE_PRIORITY_SAMPLING_UNSET; DDTRACE_G(inferred_span_created) = false; + ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link)); zend_hash_init(&DDTRACE_G(root_span_tags_preset), 8, unused, ZVAL_PTR_DTOR, 0); zend_hash_init(&DDTRACE_G(propagated_root_span_tags), 8, unused, ZVAL_PTR_DTOR, 0); zend_hash_init(&DDTRACE_G(tracestate_unknown_dd_keys), 8, unused, ZVAL_PTR_DTOR, 0); @@ -527,6 +528,8 @@ static void dd_clean_globals(void) { zend_hash_destroy(&DDTRACE_G(tracestate_unknown_dd_keys)); zend_hash_destroy(&DDTRACE_G(propagated_root_span_tags)); zend_hash_destroy(&DDTRACE_G(baggage)); + zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link)); + ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link)); if (DDTRACE_G(curl_multi_injecting_spans)) { if (GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) { diff --git a/tracer/ddtrace_globals.h b/tracer/ddtrace_globals.h index 3865b2e48f..1bfc1a9ad6 100644 --- a/tracer/ddtrace_globals.h +++ b/tracer/ddtrace_globals.h @@ -89,6 +89,7 @@ typedef struct { zend_object *git_object; bool inferred_span_created; + zval pending_upstream_span_link; // span link queued by PROPAGATION_BEHAVIOR_EXTRACT=restart; consumed on root span open HashTable resource_weak_storage; dtor_func_t resource_dtor_func; diff --git a/tracer/distributed_tracing_headers.c b/tracer/distributed_tracing_headers.c index 78d02939b3..6660d4fec0 100644 --- a/tracer/distributed_tracing_headers.c +++ b/tracer/distributed_tracing_headers.c @@ -617,6 +617,59 @@ void apply_baggage_span_tags(zend_string *key, zval *val, zend_array *meta) { void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span) { zval zv; + int behavior = get_DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT(); + + if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_IGNORE) { + // behavior=ignore: drop all extracted context including baggage + zend_hash_destroy(&result->propagated_tags); + zend_hash_destroy(&result->meta_tags); + zend_hash_destroy(&result->tracestate_unknown_dd_keys); + zend_hash_destroy(&result->baggage); + if (result->origin) { zend_string_release(result->origin); } + if (result->tracestate) { zend_string_release(result->tracestate); } + return; + } + + if (behavior == DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT_RESTART && (result->trace_id.low || result->trace_id.high)) { + // behavior=restart: zero trace_id so a fresh trace starts; upstream captured as span link + // drop _dd.p.* first so the builder does not include them in link attributes + zend_string *mk; + zend_string *to_delete[64]; + int to_delete_count = 0; + ZEND_HASH_FOREACH_STR_KEY(&result->meta_tags, mk) { + if (mk && ZSTR_LEN(mk) > 6 && strncmp(ZSTR_VAL(mk), "_dd.p.", 6) == 0) { + to_delete[to_delete_count++] = mk; + } + } ZEND_HASH_FOREACH_END(); + for (int i = 0; i < to_delete_count; i++) { + zend_hash_del(&result->meta_tags, to_delete[i]); + } + zend_hash_clean(&result->propagated_tags); + + zval link_zv; + object_init_ex(&link_zv, ddtrace_ce_span_link); + ddtrace_span_link *link = (ddtrace_span_link *)Z_OBJ(link_zv); + ddtrace_build_span_link_from_result(result, link); + + zval reason_val; + ZVAL_STR(&reason_val, zend_string_init(ZEND_STRL("reason"), 0)); + zval reason_str; + ZVAL_STR(&reason_str, zend_string_init(ZEND_STRL("propagation_behavior_extract"), 0)); + zend_hash_update(Z_ARR(link->property_attributes), Z_STR(reason_val), &reason_str); + zval_ptr_dtor(&reason_val); + + result->trace_id = (datadog_trace_id){0}; + result->parent_id = 0; + + if (span) { + zend_array *links = ddtrace_property_array(&span->property_links); + zend_hash_next_index_insert(links, &link_zv); + } else { + zval_ptr_dtor(&DDTRACE_G(pending_upstream_span_link)); + ZVAL_COPY_VALUE(&DDTRACE_G(pending_upstream_span_link), &link_zv); + } + } + zend_array *root_meta = span ? ddtrace_property_array(&span->property_meta) : &DDTRACE_G(root_span_tags_preset); if (span) { zend_string *tagname; diff --git a/tracer/distributed_tracing_headers.h b/tracer/distributed_tracing_headers.h index 9956444431..2acb2744cb 100644 --- a/tracer/distributed_tracing_headers.h +++ b/tracer/distributed_tracing_headers.h @@ -2,6 +2,7 @@ #define DD_DISTRIBUTED_TRACING_HEADERS_H #include "ddtrace.h" +#include "span.h" #include "priority_sampling/priority_sampling.h" #include @@ -24,5 +25,6 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result *result, ddtrace_root_span_data *span); bool ddtrace_read_zai_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data); bool ddtrace_read_array_header(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data); +void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link); #endif // DD_DISTRIBUTED_TRACING_HEADERS_H diff --git a/tracer/functions.c b/tracer/functions.c index aca31566a7..edb9557390 100644 --- a/tracer/functions.c +++ b/tracer/functions.c @@ -231,6 +231,22 @@ PHP_METHOD(DDTrace_SpanLink, jsonSerialize) { RETURN_ARR(array); } +void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *result, ddtrace_span_link *link) { + ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result->trace_id)); + ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result->parent_id)); + array_init(&link->property_attributes); + zend_hash_copy(Z_ARR(link->property_attributes), &result->meta_tags, NULL); + + zend_string *propagated_tags = ddtrace_format_propagated_tags(&result->propagated_tags, &result->meta_tags); + zend_string *full_tracestate = ddtrace_format_tracestate(result->tracestate, 0, result->origin, result->priority_sampling, propagated_tags, &result->tracestate_unknown_dd_keys); + if (propagated_tags) { + zend_string_release(propagated_tags); + } + if (full_tracestate) { + ZVAL_STR(&link->property_trace_state, full_tracestate); + } +} + static ddtrace_distributed_tracing_result dd_parse_distributed_tracing_headers_function(INTERNAL_FUNCTION_PARAMETERS, bool *success); ZEND_METHOD(DDTrace_SpanLink, fromHeaders) { bool success; @@ -245,19 +261,7 @@ ZEND_METHOD(DDTrace_SpanLink, fromHeaders) { return; } - ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result.trace_id)); - ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result.parent_id)); - array_init(&link->property_attributes); - zend_hash_copy(Z_ARR(link->property_attributes), &result.meta_tags, NULL); - - zend_string *propagated_tags = ddtrace_format_propagated_tags(&result.propagated_tags, &result.meta_tags); - zend_string *full_tracestate = ddtrace_format_tracestate(result.tracestate, 0, result.origin, result.priority_sampling, propagated_tags, &result.tracestate_unknown_dd_keys); - if (propagated_tags) { - zend_string_release(propagated_tags); - } - if (full_tracestate) { - ZVAL_STR(&link->property_trace_state, full_tracestate); - } + ddtrace_build_span_link_from_result(&result, link); result.meta_tags.pDestructor = NULL; // we moved values directly zend_hash_destroy(&result.meta_tags); diff --git a/tracer/span.c b/tracer/span.c index a744e020cc..c0becb2be7 100644 --- a/tracer/span.c +++ b/tracer/span.c @@ -296,6 +296,13 @@ ddtrace_span_data *ddtrace_open_span(enum ddtrace_span_dataype type) { span->parent = NULL; ddtrace_set_root_span_properties(root); + + if (primary_stack && Z_TYPE(DDTRACE_G(pending_upstream_span_link)) == IS_OBJECT) { + // attach upstream link queued by PROPAGATION_BEHAVIOR_EXTRACT=restart + zend_array *links = ddtrace_property_array(&span->property_links); + zend_hash_next_index_insert(links, &DDTRACE_G(pending_upstream_span_link)); + ZVAL_NULL(&DDTRACE_G(pending_upstream_span_link)); + } } else { ++parent_span->active_child_spans; From 6914d0228b82704ca7ad618126a862c15671f9e3 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Wed, 17 Jun 2026 12:08:59 +0200 Subject: [PATCH 03/11] test: add phpt tests for DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT Covers the three behaviors (continue, restart, ignore) and config parsing (case-insensitive values, invalid value falls back to default). Co-Authored-By: Claude Sonnet 4.6 --- .../propagation_behavior_extract_config.phpt | 45 +++++++++++++++ ...propagation_behavior_extract_continue.phpt | 32 +++++++++++ .../propagation_behavior_extract_ignore.phpt | 42 ++++++++++++++ .../propagation_behavior_extract_restart.phpt | 57 +++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 tests/ext/distributed_tracing/propagation_behavior_extract_config.phpt create mode 100644 tests/ext/distributed_tracing/propagation_behavior_extract_continue.phpt create mode 100644 tests/ext/distributed_tracing/propagation_behavior_extract_ignore.phpt create mode 100644 tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt diff --git a/tests/ext/distributed_tracing/propagation_behavior_extract_config.phpt b/tests/ext/distributed_tracing/propagation_behavior_extract_config.phpt new file mode 100644 index 0000000000..00d1431dff --- /dev/null +++ b/tests/ext/distributed_tracing/propagation_behavior_extract_config.phpt @@ -0,0 +1,45 @@ +--TEST-- +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT config parsing: case-insensitive, invalid falls back to continue +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +--FILE-- + 42, + "x-datadog-parent-id" => 10, + ]); + + $span = DDTrace\start_span(); + $result = DDTrace\root_span()->traceId === "0000000000000000000000000000002a" ? "continue" : "restart_or_ignore"; + DDTrace\close_span(); + + return $result; +} + +// Lowercase values +echo "continue: " . check_behavior("continue") . "\n"; +echo "restart: " . check_behavior("restart") . "\n"; +echo "ignore: " . check_behavior("ignore") . "\n"; + +// Case-insensitive +echo "CONTINUE: " . check_behavior("CONTINUE") . "\n"; +echo "RESTART: " . check_behavior("RESTART") . "\n"; +echo "Ignore: " . check_behavior("Ignore") . "\n"; + +// Invalid value falls back to default (continue) +echo "invalid: " . check_behavior("invalid_value") . "\n"; + +?> +--EXPECT-- +continue: continue +restart: restart_or_ignore +ignore: restart_or_ignore +CONTINUE: continue +RESTART: restart_or_ignore +Ignore: restart_or_ignore +invalid: continue diff --git a/tests/ext/distributed_tracing/propagation_behavior_extract_continue.phpt b/tests/ext/distributed_tracing/propagation_behavior_extract_continue.phpt new file mode 100644 index 0000000000..3427183934 --- /dev/null +++ b/tests/ext/distributed_tracing/propagation_behavior_extract_continue.phpt @@ -0,0 +1,32 @@ +--TEST-- +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=continue inherits upstream context +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=continue +--FILE-- + 42, + "x-datadog-parent-id" => 10, + "x-datadog-sampling-priority" => 1, + "baggage" => "user.id=123", +]); + +$span = DDTrace\start_span(); +$root = DDTrace\root_span(); + +echo "trace_id: " . $root->traceId . "\n"; +echo "parent_id: " . $root->parentId . "\n"; +echo "links_count: " . count($root->links) . "\n"; + +$headers = DDTrace\generate_distributed_tracing_headers(['baggage']); +echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; + +DDTrace\close_span(); +?> +--EXPECT-- +trace_id: 0000000000000000000000000000002a +parent_id: 10 +links_count: 0 +baggage: user.id=123 diff --git a/tests/ext/distributed_tracing/propagation_behavior_extract_ignore.phpt b/tests/ext/distributed_tracing/propagation_behavior_extract_ignore.phpt new file mode 100644 index 0000000000..5b57cea6a1 --- /dev/null +++ b/tests/ext/distributed_tracing/propagation_behavior_extract_ignore.phpt @@ -0,0 +1,42 @@ +--TEST-- +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore drops all incoming context including baggage +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore +--FILE-- + 42, + "x-datadog-parent-id" => 10, + "x-datadog-sampling-priority" => 2, + "x-datadog-tags" => "_dd.p.dm=-4", + "baggage" => "user.id=123", +]); + +$span = DDTrace\start_span(); +$root = DDTrace\root_span(); + +// fresh trace: not from upstream +echo "same_as_upstream: " . ($root->traceId === "0000000000000000000000000000002a" ? "yes" : "no") . "\n"; +echo "parent_id: " . $root->parentId . "\n"; + +// no span link (context discarded entirely) +echo "links_count: " . count($root->links) . "\n"; + +// baggage dropped +$headers = DDTrace\generate_distributed_tracing_headers(['baggage']); +echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; + +// upstream sampling priority not carried over +$dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']); +echo "sampling_priority: " . ($dd_headers['x-datadog-sampling-priority'] ?? 'none') . "\n"; + +DDTrace\close_span(); +?> +--EXPECT-- +same_as_upstream: no +parent_id: 0 +links_count: 0 +baggage: none +sampling_priority: none diff --git a/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt b/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt new file mode 100644 index 0000000000..9c6a77d7eb --- /dev/null +++ b/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt @@ -0,0 +1,57 @@ +--TEST-- +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart starts fresh trace with span link +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart +DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=0 +DD_TRACE_DEBUG_PRNG_SEED=42 +--FILE-- + 42, + "x-datadog-parent-id" => 10, + "x-datadog-sampling-priority" => 1, + "x-datadog-tags" => "_dd.p.foo=bar", + "baggage" => "user.id=123", +]); + +$span = DDTrace\start_span(); +$root = DDTrace\root_span(); + +// fresh trace: different from upstream trace_id 42 +echo "same_as_upstream: " . ($root->traceId === "0000000000000000000000000000002a" ? "yes" : "no") . "\n"; + +// span link attached to root span +echo "links_count: " . count($root->links) . "\n"; + +$link = $root->links[0] ?? null; +if ($link !== null) { + // link captures upstream trace/span ids + echo "link_trace_id: " . $link->traceId . "\n"; + echo "link_span_id: " . $link->spanId . "\n"; + echo "link_reason: " . ($link->attributes['reason'] ?? 'missing') . "\n"; + // _dd.p.foo not included (was in upstream _dd.p.* tags, now dropped) + echo "link_has_foo: " . (isset($link->attributes['_dd.p.foo']) ? "yes" : "no") . "\n"; +} + +// baggage preserved +$headers = DDTrace\generate_distributed_tracing_headers(['baggage']); +echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; + +// upstream _dd.p.foo not in outbound tags +$dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']); +$tags = $dd_headers['x-datadog-tags'] ?? ''; +echo "foo_in_tags: " . (str_contains($tags, '_dd.p.foo') ? "yes" : "no") . "\n"; + +DDTrace\close_span(); +?> +--EXPECT-- +same_as_upstream: no +links_count: 1 +link_trace_id: 0000000000000000000000000000002a +link_span_id: 000000000000000a +link_reason: propagation_behavior_extract +link_has_foo: no +baggage: user.id=123 +foo_in_tags: no From 68ddc93aa8fc1154e4563b3a2365019e58000ec9 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Wed, 17 Jun 2026 12:15:03 +0200 Subject: [PATCH 04/11] fix: set implementation to B for DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT Co-Authored-By: Claude Sonnet 4.6 --- metadata/supported-configurations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index d1c93f72a8..55912015fe 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -1997,7 +1997,7 @@ ], "DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT": [ { - "implementation": "A", + "implementation": "B", "type": "string", "default": "continue" } From 3ca84286126f61d07df7c3fa4ea5607f8cb3deff Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Wed, 17 Jun 2026 17:32:49 +0200 Subject: [PATCH 05/11] Remove breaking include --- tracer/distributed_tracing_headers.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tracer/distributed_tracing_headers.h b/tracer/distributed_tracing_headers.h index 2acb2744cb..3acb2ec63d 100644 --- a/tracer/distributed_tracing_headers.h +++ b/tracer/distributed_tracing_headers.h @@ -2,7 +2,6 @@ #define DD_DISTRIBUTED_TRACING_HEADERS_H #include "ddtrace.h" -#include "span.h" #include "priority_sampling/priority_sampling.h" #include From f5db7377ca87798fa2add8e91d3249c69435bf44 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 18 Jun 2026 11:18:27 +0200 Subject: [PATCH 06/11] Fix: prevent sampling to propagate in restart mode --- tracer/distributed_tracing_headers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tracer/distributed_tracing_headers.c b/tracer/distributed_tracing_headers.c index 6660d4fec0..bf5d7825aa 100644 --- a/tracer/distributed_tracing_headers.c +++ b/tracer/distributed_tracing_headers.c @@ -660,6 +660,7 @@ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result result->trace_id = (datadog_trace_id){0}; result->parent_id = 0; + result->priority_sampling = DDTRACE_PRIORITY_SAMPLING_UNKNOWN; if (span) { zend_array *links = ddtrace_property_array(&span->property_links); From 6374daddbc13d6d9262ab2804915487432581152 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 18 Jun 2026 11:20:12 +0200 Subject: [PATCH 07/11] Own references to property attributes when creating attributes --- tracer/functions.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tracer/functions.c b/tracer/functions.c index edb9557390..56f96e9679 100644 --- a/tracer/functions.c +++ b/tracer/functions.c @@ -235,7 +235,10 @@ void ddtrace_build_span_link_from_result(ddtrace_distributed_tracing_result *res ZVAL_STR(&link->property_trace_id, datadog_trace_id_as_hex_string(result->trace_id)); ZVAL_STR(&link->property_span_id, ddtrace_span_id_as_hex_string(result->parent_id)); array_init(&link->property_attributes); - zend_hash_copy(Z_ARR(link->property_attributes), &result->meta_tags, NULL); + // The span link owns its own references to the copied values, independent of the + // result->meta_tags lifetime: callers may keep meta_tags alive and hand it to other + // consumers (e.g. the root span's meta) or destroy it right after. + zend_hash_copy(Z_ARR(link->property_attributes), &result->meta_tags, (copy_ctor_func_t)zval_add_ref); zend_string *propagated_tags = ddtrace_format_propagated_tags(&result->propagated_tags, &result->meta_tags); zend_string *full_tracestate = ddtrace_format_tracestate(result->tracestate, 0, result->origin, result->priority_sampling, propagated_tags, &result->tracestate_unknown_dd_keys); @@ -263,7 +266,8 @@ ZEND_METHOD(DDTrace_SpanLink, fromHeaders) { ddtrace_build_span_link_from_result(&result, link); - result.meta_tags.pDestructor = NULL; // we moved values directly + // The span link took its own references (zval_add_ref) on the copied values, so destroy + // meta_tags normally to release the references it still owns. zend_hash_destroy(&result.meta_tags); zend_hash_destroy(&result.propagated_tags); zend_hash_destroy(&result.tracestate_unknown_dd_keys); From 602e2be64bb20c73e2f7004531d9a543a8a54082 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Thu, 18 Jun 2026 12:58:35 +0200 Subject: [PATCH 08/11] Apply suggestion from @bwoebi Co-authored-by: Bob Weinand --- tracer/distributed_tracing_headers.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tracer/distributed_tracing_headers.c b/tracer/distributed_tracing_headers.c index bf5d7825aa..f28ccac1a9 100644 --- a/tracer/distributed_tracing_headers.c +++ b/tracer/distributed_tracing_headers.c @@ -651,12 +651,9 @@ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result ddtrace_span_link *link = (ddtrace_span_link *)Z_OBJ(link_zv); ddtrace_build_span_link_from_result(result, link); - zval reason_val; - ZVAL_STR(&reason_val, zend_string_init(ZEND_STRL("reason"), 0)); zval reason_str; ZVAL_STR(&reason_str, zend_string_init(ZEND_STRL("propagation_behavior_extract"), 0)); - zend_hash_update(Z_ARR(link->property_attributes), Z_STR(reason_val), &reason_str); - zval_ptr_dtor(&reason_val); + zend_hash_str_update(Z_ARR(link->property_attributes), ZEND_STRL("reason"), &reason_str); result->trace_id = (datadog_trace_id){0}; result->parent_id = 0; From a39e0a4aebca50f96413cc0154bc7039c0468b58 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 18 Jun 2026 11:51:15 +0200 Subject: [PATCH 09/11] emalloc instead of statically sizer array for deleting tags --- tracer/distributed_tracing_headers.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tracer/distributed_tracing_headers.c b/tracer/distributed_tracing_headers.c index f28ccac1a9..c9942b4cec 100644 --- a/tracer/distributed_tracing_headers.c +++ b/tracer/distributed_tracing_headers.c @@ -634,7 +634,7 @@ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result // behavior=restart: zero trace_id so a fresh trace starts; upstream captured as span link // drop _dd.p.* first so the builder does not include them in link attributes zend_string *mk; - zend_string *to_delete[64]; + zend_string **to_delete = emalloc(zend_hash_num_elements(&result->meta_tags) * sizeof(zend_string *)); int to_delete_count = 0; ZEND_HASH_FOREACH_STR_KEY(&result->meta_tags, mk) { if (mk && ZSTR_LEN(mk) > 6 && strncmp(ZSTR_VAL(mk), "_dd.p.", 6) == 0) { @@ -644,6 +644,7 @@ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result for (int i = 0; i < to_delete_count; i++) { zend_hash_del(&result->meta_tags, to_delete[i]); } + efree(to_delete); zend_hash_clean(&result->propagated_tags); zval link_zv; From cb5cc57762524be34475ac2f73428397aa35b1fb Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 18 Jun 2026 13:10:22 +0200 Subject: [PATCH 10/11] add check for trace id --- .../propagation_behavior_extract_restart.phpt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt b/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt index 9c6a77d7eb..8f70583bd8 100644 --- a/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt +++ b/tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt @@ -35,6 +35,9 @@ if ($link !== null) { echo "link_has_foo: " . (isset($link->attributes['_dd.p.foo']) ? "yes" : "no") . "\n"; } +$tid = $root->traceId; +echo "trace_id_valid: " . (preg_match('/^[0-9a-f]{32}$/', $tid) && $tid !== "00000000000000000000000000000000" ? "yes" : "no") . "\n"; + // baggage preserved $headers = DDTrace\generate_distributed_tracing_headers(['baggage']); echo "baggage: " . ($headers['baggage'] ?? 'none') . "\n"; @@ -44,6 +47,9 @@ $dd_headers = DDTrace\generate_distributed_tracing_headers(['datadog']); $tags = $dd_headers['x-datadog-tags'] ?? ''; echo "foo_in_tags: " . (str_contains($tags, '_dd.p.foo') ? "yes" : "no") . "\n"; +// upstream sampling priority must not leak into the fresh trace +echo "sampling_priority: " . ($dd_headers['x-datadog-sampling-priority'] ?? 'none') . "\n"; + DDTrace\close_span(); ?> --EXPECT-- @@ -53,5 +59,7 @@ link_trace_id: 0000000000000000000000000000002a link_span_id: 000000000000000a link_reason: propagation_behavior_extract link_has_foo: no +trace_id_valid: yes baggage: user.id=123 foo_in_tags: no +sampling_priority: none From c7674bd2791009c496453e6d336222cc1520a8e5 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 18 Jun 2026 13:13:32 +0200 Subject: [PATCH 11/11] clean meta tags in one iteration --- tracer/distributed_tracing_headers.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tracer/distributed_tracing_headers.c b/tracer/distributed_tracing_headers.c index c9942b4cec..dc0ce9f135 100644 --- a/tracer/distributed_tracing_headers.c +++ b/tracer/distributed_tracing_headers.c @@ -634,17 +634,11 @@ void ddtrace_apply_distributed_tracing_result(ddtrace_distributed_tracing_result // behavior=restart: zero trace_id so a fresh trace starts; upstream captured as span link // drop _dd.p.* first so the builder does not include them in link attributes zend_string *mk; - zend_string **to_delete = emalloc(zend_hash_num_elements(&result->meta_tags) * sizeof(zend_string *)); - int to_delete_count = 0; ZEND_HASH_FOREACH_STR_KEY(&result->meta_tags, mk) { if (mk && ZSTR_LEN(mk) > 6 && strncmp(ZSTR_VAL(mk), "_dd.p.", 6) == 0) { - to_delete[to_delete_count++] = mk; + zend_hash_del(&result->meta_tags, mk); } } ZEND_HASH_FOREACH_END(); - for (int i = 0; i < to_delete_count; i++) { - zend_hash_del(&result->meta_tags, to_delete[i]); - } - efree(to_delete); zend_hash_clean(&result->propagated_tags); zval link_zv;