[Bug]: DD_TRACE_OTEL_ENABLED: OTel spans in sampling-rejected traces leave an unclosed empty-name ddtrace span (orphans; extra memory in FrankenPHP worker mode)
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 52/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Active
- Stack technique
- php
- Domaine
- backend, observability
Piste de recherche
Start with src/DDTrace/OpenTelemetry/SpanBuilder.php, especially startSpan() around the sampling decision and the drop branch; then read the cited context handling in src/DDTrace/OpenTelemetry/Context.php. Reproduce the rejected sampling case using the linked standalone script and check whether dropped spans remain open. Done means rejected traces have no empty-name orphan spans or state persisting across worker requests.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Bug report
With DD_TRACE_OTEL_ENABLED=true and a sampling rule that rejects the trace (e.g. DD_TRACE_SAMPLING_RULES=[{"sample_rate":0.05}]), every OpenTelemetry-API span started in a rejected trace leaves behind an unclosed ddtrace span with an empty name and resource, whose parent isn't in the trace. We expect the same with Adaptive Sampling, which uses sampling rules. Kept traces and 100%-sampled traces are clean.
In FrankenPHP worker mode the leaked state survives the request:
- Memory grows faster.
- In some setups, one trace per worker accumulates hundreds of these spans for the worker's lifetime.
Source (tag 1.26.1):
src/DDTrace/OpenTelemetry/SpanBuilder.php#L190and#L210:startSpan()opens a ddtrace span with\DDTrace\start_trace_span()before and independent of the sampling decision (shouldSample()at L200).#L242-L246: when the decision is neitherRECORD_AND_SAMPLEnorRECORD_ONLY, it returnsSpan::wrap($spanContext)before setting$span->resource = $this->spanName(L246). Nothing closes the ddtrace span opened above, so it stays open with an empty name.src/DDTrace/OpenTelemetry/Context.php#L228:self::storage()->attach($currentContext); // TODO: Handle Detach. In the repro the OTel context storage still held a context between requests.- Rejected (priority −1) traces produce unsampled OTel parent contexts. The trace flags come from the ddtrace
traceparent,Context.php#L186-L188. So the SDK's defaultParentBasedsampler drops each child span, which takes the drop branch above. The repro loggedstartSpan() returned OpenTelemetry\API\Trace\NonRecordingSpan, priority=-1.
Standalone reproduction: https://gist.github.com/Ardenexal/35129bc3bddd96386c6c063e635af496 (FrankenPHP worker script plus open-telemetry/sdk 1.14.0 from Packagist, no framework; bash run.sh):
- An OTel
hook()on an interface method starts and ends an internal span. The pre-hook doesspanBuilder()->setParent(Context::getCurrent())->startSpan()plusContext::storage()->attach(); the post-hook doesdetach()plusend(). 3 hooked calls per request. - 8 workers.
- Trace sink:
ddapm-test-agentv1.66.0 sharing the network namespace. - 3,000 requests at
ab -c 8per variant, each in a fresh container.
| Variant | Traces | One-root traces | Empty-name spans | Priorities | RSS per 500 requests |
|---|---|---|---|---|---|
| 5% rule, bridge on | 3,000 | 156 | 2,844 (every rejected trace has one, as an orphan root) | −1: 2,844, 2: 156 | +8.1 MiB |
| 100%, bridge on (control) | 3,000 | 3,000 | 0 | 1: 3,000 | +3.8 MiB |
5% rule, DD_TRACE_OTEL_ENABLED=false (control) |
3,000 | 3,000 | 0 | −1: 2,845, 2: 155 | +0.4 MiB |
RSS returned to about 126–128 MiB after a worker restart in every variant. Literal result line:
RESULT 1-rules5-otel/final (after worker restart): traces=3000 spans=6312 one_root_traces=156 multi_root_traces=2844 traces_gt100_spans=0 empty_name_spans=2844 (kept=0 rejected=2844 as_root=2844) priorities={'-1': 2844, '2': 156}
Per-worker accumulation (partially reproduced). Add a hooked call that runs after frankenphp_handle_request() returns (as Symfony's terminate() does in worker mode), with a ddtrace-native span inside it. With those, 8 traces (one per worker) accumulated 431–531 spans each, 214–264 of them empty-name, over 60.6–77.0 s. That variant also lost 929 of 3,000 request traces. In our app the same symptom reached 1,845–2,367 empty-name spans per worker trace, with memory +25 MiB per 500 requests against +10 at 100%.
Notes:
- The bridge grows RSS in worker mode even at 100% sampling (+3.8 against +0.4 MiB per 500 requests with it off). Rejection roughly doubles it.
OTEL_TRACES_SAMPLER=always_onmakes the orphans disappear, but it isn't a clean workaround. In rejected traces the OTel spans then carry priority 1 while their root carries −1, which we suspect a real Agent treats as separately kept chunks. That's an inference, not verified.- Observed with the test agent only; worker mode on vs off was not compared.
Expected: a span the sampler drops must not leave an open ddtrace span: close or discard it, or don't open it before sampling. Rejected traces shouldn't carry empty-name orphans, and nothing should persist across requests in worker mode.
PHP version
PHP 8.3.36 (cli) (built: Oct 21 2026 16:00:24) (ZTS). FrankenPHP v1.13.2, worker mode.
Tracer or profiler version
ddtrace 1.26.1 (ZTS build). The repro doesn't load the profiler.
Installed extensions
ddtrace (emulated opentelemetry) plus the official image's defaults. open-telemetry/sdk 1.14.0, open-telemetry/api 1.9.0, open-telemetry/context 1.5.0.
Output of phpinfo()
Not attached.
Upgrading from
New installation; previously the opentelemetry PECL extension without ddtrace.
- Langage dominant
- PHP
- Étoiles
- 558
- Forks
- 192
- Merge moyen
- 2 j 3 h
- PR mergées (30 j)
- 104
Préparer son environnement
- Fournit un Dockerfile ou un fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de DataDog/dd-trace-php
-
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
DataDog/dd-trace-php#4371 · 5 commentaires · 1 réaction ·
Les mainteneurs répondent en général sous 1 jour
-
🐛 bug
Difficulté 4/5 3-5 jours Accessibilité débutants 42/100
DataDog/dd-trace-php#4192 · 5 commentaires · 3 réactions ·
Les mainteneurs répondent en général sous 1 jour
-
🐛 bug
Difficulté 4/5 3-5 jours Accessibilité débutants 38/100
DataDog/dd-trace-php#4042 · 10 commentaires · 2 réactions ·
Les mainteneurs répondent en général sous 1 jour
-
feature-request
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
DataDog/dd-trace-php#4031 · 2 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
🐛 bug cat:app-crash
Difficulté 4/5 3-5 jours Accessibilité débutants 35/100
DataDog/dd-trace-php#3975 · 4 commentaires ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de DataDog/dd-trace-php
Issues similaires
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
Les mainteneurs répondent en général sous 1 jour
-
0. Needs triage bug
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
nextcloud/fulltextsearch#1061 ·
Les mainteneurs répondent en général sous 1 jour
-
[Resource]: Claude KitOuverteresource-submission validation-passed
Difficulté 2/5 1-3 heures Accessibilité débutants 76/100
hesreallyhim/awesome-claude-code#3375 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
C-CDA export: allergy concern act code, policy activity translation and empty id/@extension fail conformancePeut-être pris @doctorman l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100