Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

[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)

Ouverte
#4,366 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

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

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#L190 and #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 neither RECORD_AND_SAMPLE nor RECORD_ONLY, it returns Span::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 default ParentBased sampler drops each child span, which takes the drop branch above. The repro logged startSpan() 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 does spanBuilder()->setParent(Context::getCurrent())->startSpan() plus Context::storage()->attach(); the post-hook does detach() plus end(). 3 hooked calls per request.
  • 8 workers.
  • Trace sink: ddapm-test-agent v1.66.0 sharing the network namespace.
  • 3,000 requests at ab -c 8 per 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_on makes 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

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de DataDog/dd-trace-php

Toutes les issues de DataDog/dd-trace-php

Issues similaires

Plus d'issues PHP

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.