Skip to content

ci(py-315): unblock cp315 wheel builds on the stock manylinux image - #19865

Closed
vlad-scherbich wants to merge 9 commits into
mainfrom
vlad/py315-no-rebuild-experiment
Closed

ci(py-315): unblock cp315 wheel builds on the stock manylinux image#19865
vlad-scherbich wants to merge 9 commits into
mainfrom
vlad/py315-no-rebuild-experiment

Conversation

@vlad-scherbich

@vlad-scherbich vlad-scherbich commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Description

main already builds cp315 wheels — cp315-cp315 is in .PYTHON_TAGS under
allow_failure: true — but the jobs can't produce anything because the package
metadata refuses 3.15. This is the smallest change that lets them run.

  1. requires-python: >=3.9,<3.15>=3.9,<3.16. The PyPI 3.15 classifier is
    deliberately not added; that waits on chore: wrapping context support for Python 3.15 #17849.
  2. bytecode>=0.18.1,<1 for python_version>='3.15', with the 3.13/3.14 rows
    bounded below 3.15. Applied to pyproject.toml and both requirements.csv
    mirrors; the lint dependency group's pin is split the same way so
    uv run --only-group lint still resolves on 3.15.
  3. validate-ddtrace-package.py: cp315 joins PYTHON_TAGS but is required on no
    platform, and cp315 Linux wheels are absorbed as optional rather than flagged
    unexpected. Every cp315 build is allow_failure, so requiring one would let a
    best-effort failure hard-fail ddtrace package and mask the signal.

No cp315 build machinery is added; .gitlab/package.yml is byte-identical to main.

Why (context, not part of this diff)

Does building cp315 manylinux2014 wheels need an in-job CPython rebuild? #19861 is
the arm that assumes yes, via a 184-line ensure-cp315-rc1.sh. This is the arm that
assumes no.

Verified: the image's manylinux-interpreters cannot upgrade an already-present
CPython, and python_versions.json has no CPython at all, so a rebuild is the only
in-image option. All four derived images are FROM pypa 2026.05.13-1, which ships
3.15.0b1 — and musllinux cp315 wheels build on that same b1 base today. The mirror
has nothing newer than 2026.05.13-1, so a newer base isn't available without a
DataDog/images change.

Inferred, and the point of this PR: the manylinux SIGSEGV that motivated the rebuild
was observed while PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 was set — introduced in
9ce758c134, unset in 55d77a285d, a window containing both crash reports
(589c9eb129, ac417a9f41). Forcing abi3 is what compiled out PyContextVar_* and
broke the musl link; a glibc crash is a plausible second symptom of the same cause.
Plausible, not proven. This PR decides it.

Reading the result

Watch build linux for PYTHON_TAG: cp315-cp315. Success means #19861's rebuild
machinery was working around a self-inflicted crash. Another SIGSEGV means the b1
theory was right, and #19861 becomes the path forward. ddtrace package should stay
green either way.

Validator change verified locally: no cp315 → pass; all four cp315 Linux wheels →
pass with zero unexpected warnings; a cp314 wheel removed → fails with
Missing wheels: 1, so the tolerance doesn't mask real regressions.

Notes

requires-python is the only change reaching beyond CI: a built package installs
under 3.15 while the stack is incomplete, though nothing advertises 3.15. The
bytecode markers are mutually exclusive, so 3.9–3.14 resolution is unchanged.

This branch shares its first seven commits with #19861 and then reverts the rebuild
machinery, so the Commits tab shows ensure-cp315-rc1.sh added and deleted. Review
git diff main...HEAD, and squash-merge.

Root cause: stale manylinux2014 derived images (d2b8243 / May 2026) fail cp315
wheel builds while rebuilt musllinux images (233089d) succeed, leaving S3 with
musllinux-only cp315 wheels that prof-python-3.15 glibc images cannot install.

- Route cp315 manylinux2014 builds through the pypa 2026.05.13-1 mirror until
  derived manylinux images are rebuilt
- Split the build-linux matrix so cp315 skips the stale derived manylinux tag
- Lift requires-python to <3.16 and bytecode>=0.18.1 for 3.15 (PyPI 3.15
  classifier deferred until the package is PyPI-ready)
- Add cp315 to ddtrace package validation and drop cp315 allow_failure
The dd mirror pypa tag 2026.05.13-1 still ships cp315 as 3.15.0b1 and the
native build SIGSEGVs; use quay.io 2026.08.24-1 until the mirror catches up.
Restore musllinux cp315 allow_failure, sync requirements.csv, and add a reno.
GitLab third-party-registry policy blocks quay.io. Revert to the mirrored
pypa manylinux2014 2026.05.13-1 image and rebuild cp315 as 3.15.0rc1 in
before_script because the mirror still ships 3.15.0b1, which SIGSEGVs during
the native wheel build. Drop ensure-cp315-rc1.sh once DataDog/images mirrors
>= 2026.08.04-1.
The runtime manylinux2014 image ships build-cpython.sh but not cosign,
which is only copied into pypa's intermediate build_cpython stage.
finalize-one.sh bootstraps pip for the newly built interpreter by running
/usr/local/bin/cpython3.15, a shim that execs /opt/python/cp315-cp315/bin/python,
and then ends with a plain "ln -s" into /opt/python. Deleting that directory up
front broke the pip bootstrap; leaving it in place would break the symlink step.
Free only the /opt/python name, repoint the shim at the stale prefix for the
bootstrap, and assert the result is rc/final so a wheel can never be built
against 3.15.0b1.

Also install the extra manylinux build deps best-effort: EPEL-7 aarch64 has no
libzstd-devel, and zstd-sys vendors its own zstd source, so a missing package
must not abort the job.

Includes temporary stdout/NDJSON instrumentation for CI diagnosis.
"ddtrace package" demanded cp315 macOS and Windows wheels that no job
produces, because cp315 went into PYTHON_TAGS and that list is a full
cross-product over BASE_PLATFORMS. Require cp315 only on manylinux, and
tolerate musllinux cp315 either way since it is still allow_failure:
requiring it would have let a best-effort build fail the pipeline.

Also drop the debug scaffolding this investigation left behind. setup.py
is back to its upstream state, and the cp315 rebuild scripts keep the
comments explaining the missing headers in the runtime image without the
NDJSON probes. The now-unreferenced AARCH64_IMAGES and X86_64_IMAGES
anchors go too.
Experiment to check whether ensure-cp315-rc1.sh was ever necessary. Its
premise was that the mirrored pypa 2026.05.13-1 base ships 3.15.0b1 and
that b1 SIGSEGVs the native wheel build. The first half holds; the second
looks contaminated.

DataDog/images dd-trace-py/{manylinux2014,musllinux_1_2}_{x86_64,aarch64}
Dockerfiles, read at the SHAs baked into the image tags in use (d2b8243
and 233089d), are all FROM pypa 2026.05.13-1. Upstream added CPython 3.15
on 2026-05-07 as b1 and bumped to b2 on 2026-06-03, so every one of those
images ships b1 -- and musllinux cp315 wheels build on it today. Meanwhile
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 was set from the first commit of
this branch until the most recent one, spanning every commit that reported
the manylinux SIGSEGV. Forcing abi3 is what removed PyContextVar_* on
musl; on glibc it plausibly crashed instead of failing to link.

So: restore package.yml to its state on main, which already builds cp315
on manylinux and musllinux with allow_failure, and drop both helper
scripts. install-manylinux-build-deps.sh is redundant regardless -- the
derived Dockerfiles already yum install autoconf, automake, libtool,
openssl-devel, openssl-static and (on x86_64) libzstd.

The validator requires no cp315 wheel here, since every cp315 build is
allow_failure, so "ddtrace package" cannot cascade and the signal comes
from the build job alone. A missing cp39-cp314 wheel still fails.

If cp315 manylinux2014 wheels appear, the 209 lines of rebuild machinery
were working around a self-inflicted crash and stay deleted. If it
SIGSEGVs, revert this commit.
@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented Aug 26, 2026

Copy link
Copy Markdown

Pipelines  Tests

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 6 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | build linux serverless: [arm64, cp315-cp315, v113741357-d2b8243-manylinux2014_aarch64, 1] — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

Changelog | Validate changelog

View more details · View in GitHub Actions

Release note not found during changelog validation

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741238-d2b8243-manylinux2014_x86_64, 1]

View more details · View in GitLab

View all 6 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 4d6ed15 | Docs | View more details | Give us feedback!

@vlad-scherbich vlad-scherbich added the changelog/no-changelog A changelog entry is not required for this PR. label Aug 26, 2026
@vlad-scherbich vlad-scherbich changed the title Vlad/py315 no rebuild experiment ci(py-315): unblock cp315 wheel builds on the stock manylinux image Aug 26, 2026
@pr-commenter

pr-commenter Bot commented Aug 26, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-26 01:15:45

Comparing candidate commit 4d6ed15 in PR branch vlad/py315-no-rebuild-experiment with baseline commit 434cf39 in branch main.

📊 Benchmarking dashboard

Found 0 performance improvements and 8 performance regressions! Performance is the same for 577 metrics, 10 unstable metrics, 1 known flaky benchmarks, 17 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:httppropagationextract-wsgi_valid_headers_all

  • 🟥 execution_time [+428.781ns; +475.390ns] or [+7.931%; +8.793%]

scenario:httppropagationinject-ids_only

  • 🟥 execution_time [+1.905µs; +2.075µs] or [+10.071%; +10.973%]

scenario:iastaspects-add_aspect

  • 🟥 execution_time [+15.897µs; +18.829µs] or [+15.527%; +18.391%]

scenario:iastaspects-upper_aspect

  • 🟥 execution_time [+62.906µs; +68.185µs] or [+27.044%; +29.313%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+132.429µs; +138.773µs] or [+33.063%; +34.647%]

scenario:iastaspectssplit-rsplit_aspect

  • 🟥 execution_time [+18.596µs; +23.423µs] or [+12.949%; +16.310%]

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

  • 🟥 execution_time [+425.760ns; +489.491ns] or [+15.619%; +17.957%]

scenario:tracer-small

  • 🟥 execution_time [+27.010µs; +29.113µs] or [+8.004%; +8.627%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-792.603ns; +689.745ns] or [-7.151%; +6.223%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-28.858ns; +37.689ns] or [-4.747%; +6.200%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-1659.565ns; +1683.248ns] or [-9.672%; +9.810%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1102.790ns; +1396.755ns] or [-8.541%; +10.818%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-321.053ns; +337.015ns] or [-8.718%; +9.152%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-260.313ns; +246.696ns] or [-8.921%; +8.454%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-74.863ns; +69.738ns] or [-6.624%; +6.170%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-4028.335ns; +3798.134ns] or [-9.992%; +9.421%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-952.734ns; +593.732ns] or [-11.682%; +7.280%]

scenario:packagesupdateimporteddependencies-import_many_stdlib_cached

  • unstable execution_time [-56.651µs; +61.098µs] or [-9.086%; +9.799%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:span-start

  • 🟥 execution_time [+1.625ms; +1.797ms] or [+10.863%; +12.014%]

Known flaky benchmarks without significant changes:

  • scenario:errortrackingflasksqli-baseline
  • scenario:flasksimple-iast-get
  • scenario:iastaspects-casefold_aspect
  • scenario:iastaspects-casefold_noaspect
  • scenario:iastaspects-index_aspect
  • scenario:iastaspects-ljust_noaspect
  • scenario:iastaspects-lower_aspect
  • scenario:iastaspects-replace_aspect
  • scenario:iastaspects-swapcase_aspect
  • scenario:iastaspects-title_noaspect
  • scenario:iastaspects-translate_aspect
  • scenario:iastaspects-translate_noaspect
  • scenario:iastaspects-upper_noaspect
  • scenario:packagespackageforrootmodulemapping-cache_off
  • scenario:packagespackageforrootmodulemapping-cache_on
  • scenario:sethttpmeta-all-enabled
  • scenario:telemetryaddmetric-record-100-metrics

@vlad-scherbich

Copy link
Copy Markdown
Contributor Author

Closing in favor of #19861

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/no-changelog A changelog entry is not required for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant