Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ manifest:
tests/parametric/test_dynamic_configuration.py::TestDynamicConfigV2: '>=1.16.0'
tests/parametric/test_dynamic_configuration.py::TestDynamicConfigV2::test_tracing_client_tracing_tags: missing_feature
tests/parametric/test_ffe/test_dynamic_evaluation.py::Test_Feature_Flag_Dynamic_Evaluation: v1.21.0-dev
tests/parametric/test_ffe/test_span_enrichment.py: missing_feature
tests/parametric/test_ffe/test_span_enrichment.py: v1.21.0-dev
tests/parametric/test_headers_b3.py::Test_Headers_B3::test_headers_b3_migrated_extract_invalid:
- declaration: missing_feature (Need to remove b3=b3multi alias)
component_version: <1.16.0
Expand Down
31 changes: 30 additions & 1 deletion utils/build/docker/php/parametric/server.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,21 @@ function remappedSpanKind($spanKind) {
'message' => 'Failed to load FFE test configuration',
]));
}
} elseif (function_exists('dd_trace_internal_fn')) {
// No inline configuration: the test pushed a UFC config via Remote Config and the agent has
// already ACKnowledged it. In this long-running CLI server the SIGVTALRM-driven remote-config
// refresh is starved (the amphp event loop spends most of its time blocked in IO, not burning
// CPU time), so the worker would otherwise never apply the pushed UFC and evaluation would
// return default values. await_ffe_config actively pumps remote configs until the FFE config
// is applied (mirrors await_agent_info), so the subsequent /ffe/evaluate sees the loaded UFC.
dd_trace_internal_fn('await_ffe_config');
}

$ffeClient = new \DDTrace\FeatureFlags\Client();

return jsonResponse(['success' => true]);
}));
$router->addRoute('POST', '/ffe/evaluate', new ClosureRequestHandler(function (Request $req) use (&$ffeClient) {
$router->addRoute('POST', '/ffe/evaluate', new ClosureRequestHandler(function (Request $req) use (&$ffeClient, &$spans) {
if ($ffeClient === null) {
$ffeClient = new \DDTrace\FeatureFlags\Client();
}
Expand All @@ -283,8 +291,18 @@ function remappedSpanKind($spanKind) {
if (!is_array($attributes)) {
$attributes = [];
}
// The test client sends span_id as a STRING (see _test_client_parametric.py:814-815). $spans is
// keyed by $span->id and PHP coerces int-like string keys, so an isset() lookup matches directly.
$spanId = arg($req, 'span_id');

try {
// Re-activate the caller-supplied root span around the eval, mirroring /trace/span/start's
// switch_stack re-activation, so the ffe_* tags (Phase 2) land on the test's span. An
// unknown/missing span_id skips the switch_stack and evaluates normally -- never throw (T-01-DOS).
if ($spanId !== null && isset($spans[$spanId])) {
\DDTrace\switch_stack($spans[$spanId]);
}

$details = ffeEvaluate(
$ffeClient,
arg($req, 'flag'),
Expand Down Expand Up @@ -436,6 +454,7 @@ function remappedSpanKind($spanKind) {
}

$span = $spans[$span_id];
$isRoot = ($span->parent ?? null) === null;
\DDTrace\switch_stack($span);
$spansDistributedTracingHeaders[$span_id] = \DDTrace\generate_distributed_tracing_headers();
\DDTrace\close_span();
Expand All @@ -444,6 +463,16 @@ function remappedSpanKind($spanKind) {

$activeSpan = $span->parent ?? end($spans) ?? null;

// Closing a ROOT span finishes the trace. In this long-running CLI server the
// tracer only enqueues the finished trace to the async send-queue; the sidecar
// drains it on its own flush interval, which races wait_for_num_traces and can
// leave the agent with 0 traces. Synchronously flush on root close so the trace
// (carrying the ffe_* enrichment tags written on root finish) deterministically
// reaches the agent before the test reads it. Mirrors /trace/span/flush.
if ($isRoot && function_exists('dd_trace_synchronous_flush')) {
dd_trace_synchronous_flush(1000);
}

return jsonResponse([]);
}));
$router->addRoute('POST', '/trace/span/flush', new ClosureRequestHandler(function () use (&$spans) {
Expand Down
Loading