Skip to content

fix(telemetry): tolerate malformed dist-info in get_distributions - #17994

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 10 commits into
mainfrom
labbati/ddtrace-get-distributions-resilience
May 11, 2026
Merged

fix(telemetry): tolerate malformed dist-info in get_distributions#17994
gh-worker-dd-mergequeue-cf854d[bot] merged 10 commits into
mainfrom
labbati/ddtrace-get-distributions-resilience

Conversation

@labbati

@labbati labbati commented May 10, 2026

Copy link
Copy Markdown
Member

Problem

ddtrace.internal.packages.get_distributions does strict metadata["name"] access. On environments where one installed dist has malformed METADATA (missing Name:, unparseable PKG-INFO) and missing-key access raises (importlib_metadata backport, -W error::DeprecationWarning, future Python), the call raises and @callonce caches the exception for the lifetime of the process.

Impact

The telemetry dependency tracker (added in #17593) calls get_distributions per imported module on every heartbeat, wrapped in try/except + log.debug(exc_info=True). Every call after the first re-raises the cached exception, producing a chained AttributeError/KeyError traceback per module per heartbeat per worker — customer reports of ~16 GiB of stderr per pytest CI job under uv venv --system-site-packages trace back to this.

The same bug exists, less loudly, in _package_for_root_module_mapping and the <3.10 _packages_distributions fallback: one bad dist collapses the whole mapping to None, silently breaking is_third_party / filename_to_package for the rest of the process.

Solution

Per-dist try/except in all three functions: skip malformed entries individually, return what could be parsed, warn once per bad dist (deduped via _BAD_DISTS_WARNED). The update_imported_dependencies shutdown-hardening guard from #17593 is unchanged — it still defends against shutdown-time iterator failures that can't be caught from inside get_distributions.

Verification: end-to-end against the customer reproducer (50 modules + one bad dist on path) — pre-fix logs 50 chained tracebacks, post-fix logs 0 (one diagnostic Skipping distribution warning). scripts/run-tests --venv 803a341 -- tests/internal/test_packages* → 7 passed.

Backport candidate to 4.8.x. Refs: #17593

Perf

While we added atry/except it is not generating a sigifnificant impact on python < 3.11, as the leading overhead comes from I/O

 ┌─────────┬──────────┬────────────┬─────────────────┐
 │ Python  │ plain    │ try/except │ Δ per iter      │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.10.13 │ 26.39 ns │ 28.88 ns   │ +2.5 ns         │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.11.8  │ 22.84 ns │ 23.30 ns   │ +0.5 ns (noise) │
 └─────────┴──────────┴────────────┴─────────────────┘

For get_distributions specifically: it's @callonce (runs once per worker) over ~150 dists and is
dominated by METADATA file I/O (~27 ms total on 3.10). The worst-case added cost is 2.5 ns × 150 ≈
0.4 µs, six orders of magnitude under the function's runtime — invisible in an end-to-end bench
against real importlib.metadata.distributions().

🤖 Generated with Claude Code

A single installed distribution with malformed or unreadable METADATA
(missing ``Name:`` header, unparseable PKG-INFO, transient IOError during
interpreter teardown) made ``get_distributions`` raise.  ``@callonce``
cached that exception for the lifetime of the process, and the per-module
``try/except + log.debug(..., exc_info=True)`` added by #17593 logged the
chained ``AttributeError``/``KeyError`` traceback once per imported module
per heartbeat per worker -- gigabytes of stderr per CI job on customer
environments running pytest under ``uv venv --system-site-packages``.

The strict ``metadata["name"]`` access has been latent since well before
4.5.  In 4.5 a malformed dist made the heartbeat raise once per cycle
(handled silently by ``threading.excepthook``); the per-module guard added
in 4.8 to harden against shutdown-time iterator failures inadvertently
turned that into per-module log spam every heartbeat forever.

Fix: per-dist ``try/except`` in ``get_distributions``,
``_package_for_root_module_mapping``, and the ``<3.10``
``_packages_distributions`` fallback.  Skip malformed entries individually,
return what could be parsed, warn once per bad dist (deduped via
``_BAD_DISTS_WARNED``).  Use ``metadata["Name"] or ""`` (canonical PEP 566
capitalization, email-Message lookup is case-insensitive) to neutralize
both the legacy silent-None path and the future-strict KeyError path
emitted by ``importlib.metadata._adapters._warn`` /
``importlib_metadata`` backport.

The hardening guard in ``update_imported_dependencies`` is unchanged --
it still defends against shutdown-time iterator failures that cannot be
caught from inside ``get_distributions``.

Refs: #17593

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented May 10, 2026

Copy link
Copy Markdown

Codeowners resolved as

ddtrace/internal/packages.py                                            @DataDog/apm-core-python
releasenotes/notes/fix-get-distributions-malformed-metadata-879c9025d81a0311.yaml  @DataDog/apm-python
tests/internal/test_packages_resilience.py                              @DataDog/apm-core-python

@pr-commenter

pr-commenter Bot commented May 10, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-05-11 16:25:31

Comparing candidate commit c4023d3 in PR branch labbati/ddtrace-get-distributions-resilience with baseline commit 2a937b8 in branch main.

Found 0 performance improvements and 5 performance regressions! Performance is the same for 590 metrics, 4 unstable metrics.

scenario:iastaspects-index_aspect

  • 🟥 execution_time [+12.918µs; +15.281µs] or [+10.391%; +12.291%]

scenario:iastaspects-stringio_aspect

  • 🟥 execution_time [+637.380µs; +679.113µs] or [+16.517%; +17.599%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+88.780µs; +97.190µs] or [+21.064%; +23.060%]

scenario:span-start

  • 🟥 execution_time [+1.328ms; +1.477ms] or [+8.540%; +9.501%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+194.613ns; +230.208ns] or [+9.312%; +11.015%]

labbati and others added 5 commits May 10, 2026 21:03
- Revert ``metadata["Name"]`` -> ``metadata["name"]`` in ``get_distributions``
  and ``_package_for_root_module_mapping``. Email-Message lookups are
  case-insensitive so the two are runtime-equivalent; the original casing
  matches the surrounding code.
- Drop ``or ""`` coercion: the ``if name and version:`` guard already
  short-circuits on both ``None`` and ``""``.
- Drop the silent-None test (passed pre-fix too — didn't validate the fix),
  the metadata-attribute-error test (over-specified — guards a hypothetical
  refactor not covered by other tests), and merge the cache-result test
  into the keeper. Two tests remain, one per modified function, both
  load-bearing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variables assigned inside ``try`` were used after it on a path that
``except: continue`` skipped — runtime-safe but a code smell ("possibly
unbound" to a static reader). Move the use inside the try in all three
modified functions; drop the now-redundant ``continue`` at end-of-loop.

Compress both AIDEV-NOTEs to two lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ``_log_bad_dist`` now emits at DEBUG. The skip is graceful and the
  malformed dist is usually a permanent fixture of the host (e.g. system
  Python on Debian/RHEL); WARNING just polluted normal logs.
- Drop the outer try/except around ``importlib_metadata.distributions()``
  in ``_package_for_root_module_mapping``. The per-dist try/except already
  covers per-iteration failures; the iterator-creation case is consistent
  with ``get_distributions`` which doesn't guard it either.
- Fix test isolation in ``reset_packages_caches``: clear the @CallOnce
  cache on teardown too. The fixture site-packages used by these tests
  was bleeding into ``test_filename_to_package`` running next in the
  same xdist worker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The iterator-creation case is genuinely catastrophic (no dists enumerable
at all) and worth a one-shot ``LOG.warning(..., exc_info=True)`` rather
than letting @CallOnce cache the exception and re-raise from
``filename_to_package`` and friends every time.

Per-dist failures stay at DEBUG via ``_log_bad_dist``.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@labbati
labbati marked this pull request as ready for review May 11, 2026 08:05
@labbati
labbati requested review from a team as code owners May 11, 2026 08:05
@labbati
labbati requested review from florentinl and juanjux May 11, 2026 08:05
@labbati
labbati force-pushed the labbati/ddtrace-get-distributions-resilience branch from ae43e5b to a7d63c6 Compare May 11, 2026 08:14
@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.7 and it does not conflict with that branch.
The command used to test backporting was

git checkout 4.7 && git cherry-pick -x --mainline 1 c63b1b5fdb097348a7d66f4c8a57ed9002cb7517

@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.8 and it does not conflict with that branch.
The command used to test backporting was

git checkout 4.8 && git cherry-pick -x --mainline 1 c63b1b5fdb097348a7d66f4c8a57ed9002cb7517

@github-actions

Copy link
Copy Markdown
Contributor

This change is marked for backport to 4.6 and it does not conflict with that branch.
The command used to test backporting was

git checkout 4.6 && git cherry-pick -x --mainline 1 c63b1b5fdb097348a7d66f4c8a57ed9002cb7517

@labbati

labbati commented May 11, 2026

Copy link
Copy Markdown
Member Author

/merge -p urgent

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented May 11, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-05-11 18:02:58 UTC ℹ️ Start processing command /merge -p urgent


2026-05-11 18:03:03 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in main is approximately 53m (p90).


2026-05-11 18:07:09 UTCMergeQueue: The build pipeline contains failing jobs for this merge request

Build pipeline has failing jobs for b9729cf:

⚠️ Do NOT retry failed jobs directly (why?).

What to do next?

  • Investigate the failures and when ready, re-add your pull request to the queue!
  • If your PR checks are green, try to rebase/merge. It might be because the CI run is a bit old.
  • Any question, go check the FAQ.
Details

Since those jobs are not marked as being allowed to fail, the pipeline will most likely fail.
Therefore, and to allow other builds to be processed, this merge request has been rejected and the pipeline got canceled.

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit ba713e3 into main May 11, 2026
1159 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the labbati/ddtrace-get-distributions-resilience branch May 11, 2026 19:19
github-actions Bot added a commit that referenced this pull request May 11, 2026
…7994)

## Problem

`ddtrace.internal.packages.get_distributions` does strict `metadata["name"]` access. On environments where one installed dist has malformed METADATA (missing `Name:`, unparseable PKG-INFO) **and** missing-key access raises (`importlib_metadata` backport, `-W error::DeprecationWarning`, future Python), the call raises and `@callonce` caches the exception for the lifetime of the process.

## Impact

The telemetry dependency tracker (added in #17593) calls `get_distributions` per imported module on every heartbeat, wrapped in `try/except + log.debug(exc_info=True)`. Every call after the first re-raises the cached exception, producing a chained `AttributeError`/`KeyError` traceback per module per heartbeat per worker — customer reports of ~16 GiB of stderr per pytest CI job under `uv venv --system-site-packages` trace back to this.

The same bug exists, less loudly, in `_package_for_root_module_mapping` and the `<3.10` `_packages_distributions` fallback: one bad dist collapses the whole mapping to `None`, silently breaking `is_third_party` / `filename_to_package` for the rest of the process.

## Solution

Per-dist `try/except` in all three functions: skip malformed entries individually, return what could be parsed, warn once per bad dist (deduped via `_BAD_DISTS_WARNED`). The `update_imported_dependencies` shutdown-hardening guard from #17593 is unchanged — it still defends against shutdown-time iterator failures that can't be caught from inside `get_distributions`.

**Verification:** end-to-end against the customer reproducer (50 modules + one bad dist on path) — pre-fix logs 50 chained tracebacks, post-fix logs 0 (one diagnostic `Skipping distribution` warning). `scripts/run-tests --venv 803a341 -- tests/internal/test_packages*` → 7 passed.

Backport candidate to 4.8.x. Refs: #17593

## Perf

While we added a`try/except` it is not generating a sigifnificant impact on python < 3.11, as the leading overhead comes from I/O
```
 ┌─────────┬──────────┬────────────┬─────────────────┐
 │ Python  │ plain    │ try/except │ Δ per iter      │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.10.13 │ 26.39 ns │ 28.88 ns   │ +2.5 ns         │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.11.8  │ 22.84 ns │ 23.30 ns   │ +0.5 ns (noise) │
 └─────────┴──────────┴────────────┴─────────────────┘
```
 For `get_distributions` specifically: it's `@callonce` (runs once per worker) over ~150 dists and is
 dominated by METADATA file I/O (~27 ms total on 3.10). The worst-case added cost is 2.5 ns × 150 ≈
 0.4 µs, six orders of magnitude under the function's runtime — invisible in an end-to-end bench
 against real importlib.metadata.distributions().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: juanjux <juanjo.alvarezmartinez@datadoghq.com>
Co-authored-by: brett.langdon <brett.langdon@datadoghq.com>
(cherry picked from commit ba713e3)

Co-authored-by: Luca Abbati <luca.abbati@datadoghq.com>
github-actions Bot added a commit that referenced this pull request May 11, 2026
…7994)

## Problem

`ddtrace.internal.packages.get_distributions` does strict `metadata["name"]` access. On environments where one installed dist has malformed METADATA (missing `Name:`, unparseable PKG-INFO) **and** missing-key access raises (`importlib_metadata` backport, `-W error::DeprecationWarning`, future Python), the call raises and `@callonce` caches the exception for the lifetime of the process.

## Impact

The telemetry dependency tracker (added in #17593) calls `get_distributions` per imported module on every heartbeat, wrapped in `try/except + log.debug(exc_info=True)`. Every call after the first re-raises the cached exception, producing a chained `AttributeError`/`KeyError` traceback per module per heartbeat per worker — customer reports of ~16 GiB of stderr per pytest CI job under `uv venv --system-site-packages` trace back to this.

The same bug exists, less loudly, in `_package_for_root_module_mapping` and the `<3.10` `_packages_distributions` fallback: one bad dist collapses the whole mapping to `None`, silently breaking `is_third_party` / `filename_to_package` for the rest of the process.

## Solution

Per-dist `try/except` in all three functions: skip malformed entries individually, return what could be parsed, warn once per bad dist (deduped via `_BAD_DISTS_WARNED`). The `update_imported_dependencies` shutdown-hardening guard from #17593 is unchanged — it still defends against shutdown-time iterator failures that can't be caught from inside `get_distributions`.

**Verification:** end-to-end against the customer reproducer (50 modules + one bad dist on path) — pre-fix logs 50 chained tracebacks, post-fix logs 0 (one diagnostic `Skipping distribution` warning). `scripts/run-tests --venv 803a341 -- tests/internal/test_packages*` → 7 passed.

Backport candidate to 4.8.x. Refs: #17593

## Perf

While we added a`try/except` it is not generating a sigifnificant impact on python < 3.11, as the leading overhead comes from I/O
```
 ┌─────────┬──────────┬────────────┬─────────────────┐
 │ Python  │ plain    │ try/except │ Δ per iter      │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.10.13 │ 26.39 ns │ 28.88 ns   │ +2.5 ns         │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.11.8  │ 22.84 ns │ 23.30 ns   │ +0.5 ns (noise) │
 └─────────┴──────────┴────────────┴─────────────────┘
```
 For `get_distributions` specifically: it's `@callonce` (runs once per worker) over ~150 dists and is
 dominated by METADATA file I/O (~27 ms total on 3.10). The worst-case added cost is 2.5 ns × 150 ≈
 0.4 µs, six orders of magnitude under the function's runtime — invisible in an end-to-end bench
 against real importlib.metadata.distributions().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: juanjux <juanjo.alvarezmartinez@datadoghq.com>
Co-authored-by: brett.langdon <brett.langdon@datadoghq.com>
(cherry picked from commit ba713e3)

Co-authored-by: Luca Abbati <luca.abbati@datadoghq.com>
github-actions Bot added a commit that referenced this pull request May 11, 2026
…7994)

## Problem

`ddtrace.internal.packages.get_distributions` does strict `metadata["name"]` access. On environments where one installed dist has malformed METADATA (missing `Name:`, unparseable PKG-INFO) **and** missing-key access raises (`importlib_metadata` backport, `-W error::DeprecationWarning`, future Python), the call raises and `@callonce` caches the exception for the lifetime of the process.

## Impact

The telemetry dependency tracker (added in #17593) calls `get_distributions` per imported module on every heartbeat, wrapped in `try/except + log.debug(exc_info=True)`. Every call after the first re-raises the cached exception, producing a chained `AttributeError`/`KeyError` traceback per module per heartbeat per worker — customer reports of ~16 GiB of stderr per pytest CI job under `uv venv --system-site-packages` trace back to this.

The same bug exists, less loudly, in `_package_for_root_module_mapping` and the `<3.10` `_packages_distributions` fallback: one bad dist collapses the whole mapping to `None`, silently breaking `is_third_party` / `filename_to_package` for the rest of the process.

## Solution

Per-dist `try/except` in all three functions: skip malformed entries individually, return what could be parsed, warn once per bad dist (deduped via `_BAD_DISTS_WARNED`). The `update_imported_dependencies` shutdown-hardening guard from #17593 is unchanged — it still defends against shutdown-time iterator failures that can't be caught from inside `get_distributions`.

**Verification:** end-to-end against the customer reproducer (50 modules + one bad dist on path) — pre-fix logs 50 chained tracebacks, post-fix logs 0 (one diagnostic `Skipping distribution` warning). `scripts/run-tests --venv 803a341 -- tests/internal/test_packages*` → 7 passed.

Backport candidate to 4.8.x. Refs: #17593

## Perf

While we added a`try/except` it is not generating a sigifnificant impact on python < 3.11, as the leading overhead comes from I/O
```
 ┌─────────┬──────────┬────────────┬─────────────────┐
 │ Python  │ plain    │ try/except │ Δ per iter      │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.10.13 │ 26.39 ns │ 28.88 ns   │ +2.5 ns         │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.11.8  │ 22.84 ns │ 23.30 ns   │ +0.5 ns (noise) │
 └─────────┴──────────┴────────────┴─────────────────┘
```
 For `get_distributions` specifically: it's `@callonce` (runs once per worker) over ~150 dists and is
 dominated by METADATA file I/O (~27 ms total on 3.10). The worst-case added cost is 2.5 ns × 150 ≈
 0.4 µs, six orders of magnitude under the function's runtime — invisible in an end-to-end bench
 against real importlib.metadata.distributions().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: juanjux <juanjo.alvarezmartinez@datadoghq.com>
Co-authored-by: brett.langdon <brett.langdon@datadoghq.com>
(cherry picked from commit ba713e3)

Co-authored-by: Luca Abbati <luca.abbati@datadoghq.com>
brettlangdon pushed a commit that referenced this pull request May 11, 2026
…ckport 4.8] (#18019)

Backport #17994 to 4.8

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Luca Abbati <luca.abbati@datadoghq.com>
quinna-h pushed a commit that referenced this pull request May 11, 2026
…ckport 4.7] (#18020)

Backport #17994 to 4.7

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Luca Abbati <luca.abbati@datadoghq.com>
mabdinur pushed a commit that referenced this pull request May 11, 2026
…7994)

## Problem

`ddtrace.internal.packages.get_distributions` does strict `metadata["name"]` access. On environments where one installed dist has malformed METADATA (missing `Name:`, unparseable PKG-INFO) **and** missing-key access raises (`importlib_metadata` backport, `-W error::DeprecationWarning`, future Python), the call raises and `@callonce` caches the exception for the lifetime of the process.

## Impact

The telemetry dependency tracker (added in #17593) calls `get_distributions` per imported module on every heartbeat, wrapped in `try/except + log.debug(exc_info=True)`. Every call after the first re-raises the cached exception, producing a chained `AttributeError`/`KeyError` traceback per module per heartbeat per worker — customer reports of ~16 GiB of stderr per pytest CI job under `uv venv --system-site-packages` trace back to this.

The same bug exists, less loudly, in `_package_for_root_module_mapping` and the `<3.10` `_packages_distributions` fallback: one bad dist collapses the whole mapping to `None`, silently breaking `is_third_party` / `filename_to_package` for the rest of the process.

## Solution

Per-dist `try/except` in all three functions: skip malformed entries individually, return what could be parsed, warn once per bad dist (deduped via `_BAD_DISTS_WARNED`). The `update_imported_dependencies` shutdown-hardening guard from #17593 is unchanged — it still defends against shutdown-time iterator failures that can't be caught from inside `get_distributions`.

**Verification:** end-to-end against the customer reproducer (50 modules + one bad dist on path) — pre-fix logs 50 chained tracebacks, post-fix logs 0 (one diagnostic `Skipping distribution` warning). `scripts/run-tests --venv 803a341 -- tests/internal/test_packages*` → 7 passed.

Backport candidate to 4.8.x. Refs: #17593

## Perf

While we added a`try/except` it is not generating a sigifnificant impact on python < 3.11, as the leading overhead comes from I/O
```
 ┌─────────┬──────────┬────────────┬─────────────────┐
 │ Python  │ plain    │ try/except │ Δ per iter      │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.10.13 │ 26.39 ns │ 28.88 ns   │ +2.5 ns         │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.11.8  │ 22.84 ns │ 23.30 ns   │ +0.5 ns (noise) │
 └─────────┴──────────┴────────────┴─────────────────┘
```
 For `get_distributions` specifically: it's `@callonce` (runs once per worker) over ~150 dists and is
 dominated by METADATA file I/O (~27 ms total on 3.10). The worst-case added cost is 2.5 ns × 150 ≈
 0.4 µs, six orders of magnitude under the function's runtime — invisible in an end-to-end bench
 against real importlib.metadata.distributions().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: juanjux <juanjo.alvarezmartinez@datadoghq.com>
Co-authored-by: brett.langdon <brett.langdon@datadoghq.com>
P403n1x87 pushed a commit that referenced this pull request May 14, 2026
…7994)

## Problem

`ddtrace.internal.packages.get_distributions` does strict `metadata["name"]` access. On environments where one installed dist has malformed METADATA (missing `Name:`, unparseable PKG-INFO) **and** missing-key access raises (`importlib_metadata` backport, `-W error::DeprecationWarning`, future Python), the call raises and `@callonce` caches the exception for the lifetime of the process.

## Impact

The telemetry dependency tracker (added in #17593) calls `get_distributions` per imported module on every heartbeat, wrapped in `try/except + log.debug(exc_info=True)`. Every call after the first re-raises the cached exception, producing a chained `AttributeError`/`KeyError` traceback per module per heartbeat per worker — customer reports of ~16 GiB of stderr per pytest CI job under `uv venv --system-site-packages` trace back to this.

The same bug exists, less loudly, in `_package_for_root_module_mapping` and the `<3.10` `_packages_distributions` fallback: one bad dist collapses the whole mapping to `None`, silently breaking `is_third_party` / `filename_to_package` for the rest of the process.

## Solution

Per-dist `try/except` in all three functions: skip malformed entries individually, return what could be parsed, warn once per bad dist (deduped via `_BAD_DISTS_WARNED`). The `update_imported_dependencies` shutdown-hardening guard from #17593 is unchanged — it still defends against shutdown-time iterator failures that can't be caught from inside `get_distributions`.

**Verification:** end-to-end against the customer reproducer (50 modules + one bad dist on path) — pre-fix logs 50 chained tracebacks, post-fix logs 0 (one diagnostic `Skipping distribution` warning). `scripts/run-tests --venv 803a341 -- tests/internal/test_packages*` → 7 passed.

Backport candidate to 4.8.x. Refs: #17593

## Perf

While we added a`try/except` it is not generating a sigifnificant impact on python < 3.11, as the leading overhead comes from I/O
```
 ┌─────────┬──────────┬────────────┬─────────────────┐
 │ Python  │ plain    │ try/except │ Δ per iter      │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.10.13 │ 26.39 ns │ 28.88 ns   │ +2.5 ns         │
 ├─────────┼──────────┼────────────┼─────────────────┤
 │ 3.11.8  │ 22.84 ns │ 23.30 ns   │ +0.5 ns (noise) │
 └─────────┴──────────┴────────────┴─────────────────┘
```
 For `get_distributions` specifically: it's `@callonce` (runs once per worker) over ~150 dists and is
 dominated by METADATA file I/O (~27 ms total on 3.10). The worst-case added cost is 2.5 ns × 150 ≈
 0.4 µs, six orders of magnitude under the function's runtime — invisible in an end-to-end bench
 against real importlib.metadata.distributions().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: juanjux <juanjo.alvarezmartinez@datadoghq.com>
Co-authored-by: brett.langdon <brett.langdon@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants