Skip to content

Commit ce8e5c8

Browse files
committed
Implement PHP tracer-owned OTel thread-context storage
The OTel thread-context record is now owned and maintained by the PHP tracer itself rather than going through libdatadog's OTel thread-context record/update helpers. The tracer keeps its own local record and drives all updates through granular helpers. Storage and layout: - Store the record in ddtrace_root_span_data, placed before the embedded ddtrace_span_data, because of requirements of Zend object layout. - Move the record to sit after sampling_rule to reduce struct padding in the root prefix. - Make the record naturally 8-byte aligned: trace_id is stored as uint64_t[2], while span_id and valid are C11 atomics. Compile-time assertions guard record size, field offsets, and root placement. Update paths: - Drive context updates through granular helpers: stack attach, trace-id update, active span-id update, root-scoped attributes, and detach. - Keep root-specific detach-if-current behavior: freeing a root span clears the TLS pointer only when it currently points at that root's embedded record. Stack, fiber, close, drop, trace-id, and attribute mutation paths call the granular API directly. Tests and dependencies: - Update the affected appsec integration tests. - Restore the libdatadog gitlink and Cargo.lock to the merge-base / origin-master state, since this implementation no longer depends on the upstream libdatadog OTel thread-context record/update APIs.
1 parent 85df90d commit ce8e5c8

29 files changed

Lines changed: 1143 additions & 301 deletions

Cargo.lock

Lines changed: 3 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/OtelThreadContextTests.groovy

Lines changed: 429 additions & 32 deletions
Large diffs are not rendered by default.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
$rootSpan = \DDTrace\root_span();
4+
if (!$rootSpan) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing root span']);
7+
return;
8+
}
9+
10+
$childSpan = \DDTrace\start_span();
11+
if (!$childSpan) {
12+
http_response_code(500);
13+
echo json_encode(['error' => 'missing child span']);
14+
return;
15+
}
16+
17+
$childSpanId = $childSpan->hexId();
18+
\DDTrace\close_span();
19+
20+
$waited = \datadog\appsec\testing\wait_for_debugger();
21+
22+
header('Content-Type: application/json');
23+
echo json_encode([
24+
'waited' => $waited,
25+
'trace_id' => $rootSpan->traceId,
26+
'span_id' => $rootSpan->hexId(),
27+
'child_span_id' => $childSpanId,
28+
'local_root_span_id' => $rootSpan->hexId(),
29+
'service_name' => $rootSpan->service,
30+
'service_version' => $rootSpan->version,
31+
'deployment_environment_name' => $rootSpan->env,
32+
]);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
$rootSpan = \DDTrace\root_span();
4+
if (!$rootSpan) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing root span']);
7+
return;
8+
}
9+
10+
$originalTraceId = $rootSpan->traceId;
11+
\DDTrace\consume_distributed_tracing_headers([
12+
'traceparent' => '00-11111111111111112222222222222222-3333333333333333-01',
13+
]);
14+
15+
$waited = \datadog\appsec\testing\wait_for_debugger();
16+
17+
header('Content-Type: application/json');
18+
echo json_encode([
19+
'waited' => $waited,
20+
'original_trace_id' => $originalTraceId,
21+
'trace_id' => $rootSpan->traceId,
22+
'span_id' => $rootSpan->hexId(),
23+
'local_root_span_id' => $rootSpan->hexId(),
24+
'service_name' => $rootSpan->service,
25+
'service_version' => $rootSpan->version,
26+
'deployment_environment_name' => $rootSpan->env,
27+
]);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
$rootSpan = \DDTrace\root_span();
4+
if (!$rootSpan) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing root span']);
7+
return;
8+
}
9+
10+
$childSpan = \DDTrace\start_span();
11+
if (!$childSpan) {
12+
http_response_code(500);
13+
echo json_encode(['error' => 'missing child span']);
14+
return;
15+
}
16+
17+
$childSpanId = $childSpan->hexId();
18+
$dropped = \DDTrace\try_drop_span($childSpan);
19+
20+
$waited = \datadog\appsec\testing\wait_for_debugger();
21+
22+
header('Content-Type: application/json');
23+
echo json_encode([
24+
'waited' => $waited,
25+
'dropped' => $dropped,
26+
'trace_id' => $rootSpan->traceId,
27+
'span_id' => $rootSpan->hexId(),
28+
'child_span_id' => $childSpanId,
29+
'local_root_span_id' => $rootSpan->hexId(),
30+
'service_name' => $rootSpan->service,
31+
'service_version' => $rootSpan->version,
32+
'deployment_environment_name' => $rootSpan->env,
33+
]);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
$mainRoot = \DDTrace\root_span();
4+
if (!$mainRoot) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing main root span']);
7+
return;
8+
}
9+
10+
$state = [];
11+
$fiber = new \Fiber(function () use (&$state) {
12+
$fiberRoot = \DDTrace\start_trace_span();
13+
if (!$fiberRoot) {
14+
$state['error'] = 'missing fiber root span';
15+
return;
16+
}
17+
18+
file_put_contents('/tmp/otel_context_phase', 'fiber');
19+
$state['fiber_waited'] = \datadog\appsec\testing\wait_for_debugger();
20+
$state['fiber_trace_id'] = $fiberRoot->traceId;
21+
$state['fiber_span_id'] = $fiberRoot->hexId();
22+
$state['fiber_local_root_span_id'] = $fiberRoot->hexId();
23+
24+
\Fiber::suspend();
25+
\DDTrace\close_span();
26+
});
27+
28+
$fiber->start();
29+
if (isset($state['error'])) {
30+
http_response_code(500);
31+
echo json_encode(['error' => $state['error']]);
32+
return;
33+
}
34+
35+
file_put_contents('/tmp/otel_context_phase', 'main');
36+
$mainWaited = \datadog\appsec\testing\wait_for_debugger();
37+
38+
$fiber->resume();
39+
40+
header('Content-Type: application/json');
41+
echo json_encode([
42+
'fiber_waited' => $state['fiber_waited'] ?? false,
43+
'main_waited' => $mainWaited,
44+
'main_trace_id' => $mainRoot->traceId,
45+
'main_span_id' => $mainRoot->hexId(),
46+
'main_local_root_span_id' => $mainRoot->hexId(),
47+
'fiber_trace_id' => $state['fiber_trace_id'] ?? null,
48+
'fiber_span_id' => $state['fiber_span_id'] ?? null,
49+
'fiber_local_root_span_id' => $state['fiber_local_root_span_id'] ?? null,
50+
'service_name' => $mainRoot->service,
51+
'service_version' => $mainRoot->version,
52+
'deployment_environment_name' => $mainRoot->env,
53+
]);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
$entrypointRoot = \DDTrace\root_span();
4+
if (!$entrypointRoot) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing entrypoint root span']);
7+
return;
8+
}
9+
10+
$originalService = $entrypointRoot->service;
11+
$originalVersion = $entrypointRoot->version;
12+
$originalEnv = $entrypointRoot->env;
13+
14+
$entrypointRoot->service = 'otel-thread-context-entrypoint-service';
15+
$entrypointRoot->version = '4.5.6';
16+
$entrypointRoot->env = 'otel-thread-context-entrypoint-env';
17+
18+
$nestedRoot = \DDTrace\start_trace_span();
19+
if (!$nestedRoot) {
20+
http_response_code(500);
21+
echo json_encode(['error' => 'missing nested root span']);
22+
return;
23+
}
24+
25+
$nestedRoot->service = 'otel-thread-context-nested-service';
26+
$nestedRoot->version = '7.8.9';
27+
$nestedRoot->env = 'otel-thread-context-nested-env';
28+
29+
$waited = \datadog\appsec\testing\wait_for_debugger();
30+
31+
header('Content-Type: application/json');
32+
echo json_encode([
33+
'waited' => $waited,
34+
'trace_id' => $nestedRoot->traceId,
35+
'span_id' => $nestedRoot->hexId(),
36+
'local_root_span_id' => $nestedRoot->hexId(),
37+
'service_name' => $entrypointRoot->service,
38+
'service_version' => $entrypointRoot->version,
39+
'deployment_environment_name' => $entrypointRoot->env,
40+
'original_service_name' => $originalService,
41+
'original_service_version' => $originalVersion,
42+
'original_deployment_environment_name' => $originalEnv,
43+
'entrypoint_service_name' => $entrypointRoot->service,
44+
'entrypoint_service_version' => $entrypointRoot->version,
45+
'entrypoint_deployment_environment_name' => $entrypointRoot->env,
46+
'nested_service_name' => $nestedRoot->service,
47+
'nested_service_version' => $nestedRoot->version,
48+
'nested_deployment_environment_name' => $nestedRoot->env,
49+
]);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
$rootSpan = \DDTrace\root_span();
4+
if (!$rootSpan) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing initial root span']);
7+
return;
8+
}
9+
10+
ini_set('datadog.trace.enabled', '0');
11+
file_put_contents('/tmp/otel_context_phase', 'disabled');
12+
$disabledWaited = \datadog\appsec\testing\wait_for_debugger();
13+
14+
ini_set('datadog.trace.enabled', '1');
15+
$rootSpan = \DDTrace\root_span();
16+
if (!$rootSpan) {
17+
http_response_code(500);
18+
echo json_encode(['error' => 'missing reenabled root span']);
19+
return;
20+
}
21+
22+
file_put_contents('/tmp/otel_context_phase', 'reenabled');
23+
$reenabledWaited = \datadog\appsec\testing\wait_for_debugger();
24+
25+
header('Content-Type: application/json');
26+
echo json_encode([
27+
'waited' => $reenabledWaited,
28+
'disabled_waited' => $disabledWaited,
29+
'trace_id' => $rootSpan->traceId,
30+
'span_id' => $rootSpan->hexId(),
31+
'local_root_span_id' => $rootSpan->hexId(),
32+
'service_name' => $rootSpan->service,
33+
'service_version' => $rootSpan->version,
34+
'deployment_environment_name' => $rootSpan->env,
35+
]);

appsec/tests/integration/src/test/www/base/public/otel_context_regular.php renamed to appsec/tests/integration/src/test/www/base/public/otel_context/regular.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@
1515
'trace_id' => $rootSpan->traceId,
1616
'span_id' => $rootSpan->hexId(),
1717
'local_root_span_id' => $rootSpan->hexId(),
18+
'service_name' => $rootSpan->service,
19+
'service_version' => $rootSpan->version,
20+
'deployment_environment_name' => $rootSpan->env,
1821
]);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
$rootSpan = \DDTrace\root_span();
4+
if (!$rootSpan) {
5+
http_response_code(500);
6+
echo json_encode(['error' => 'missing root span']);
7+
return;
8+
}
9+
10+
$rootSpan->service = 'otel-thread-context-root-service';
11+
$rootSpan->version = '3.4.5';
12+
$rootSpan->env = 'otel-thread-context-root-env';
13+
14+
$waited = \datadog\appsec\testing\wait_for_debugger();
15+
16+
header('Content-Type: application/json');
17+
echo json_encode([
18+
'waited' => $waited,
19+
'trace_id' => $rootSpan->traceId,
20+
'span_id' => $rootSpan->hexId(),
21+
'local_root_span_id' => $rootSpan->hexId(),
22+
'service_name' => $rootSpan->service,
23+
'service_version' => $rootSpan->version,
24+
'deployment_environment_name' => $rootSpan->env,
25+
]);

0 commit comments

Comments
 (0)