Visitar URL original
Observed memory leak in ssl library: Python 3.14 GC issue · Issue #142516 · python/cpython · GitHub
Skip to content

Observed memory leak in ssl library: Python 3.14 GC issue #142516

Description

@HenriBlacksmith

Bug report

Bug description:

I already opened a bug on urllib3 for this issue, it looks that may application using MSAL for Python library (which relies on requests , which relies on urllib3, which relies on ssl) is leaking memory when being upgraded from Python 3.13 to Python 3.14 (I tested 3.14.{0, 1, 2})

The details are explained in the urllib3 ticket including outputs from memray and it points to ssl package (precisely ssl.SSLContext.load_verify_locations), see: urllib3/urllib3#3738

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs

Activity

  1. vstinner commented on Dec 10, 2025

    @vstinner
    Member

    The details are explained in the urllib3 ticket including outputs from memray and it points to ssl package (precisely ssl.SSLContext.load_verify_locations), see: urllib3/urllib3#3738

    Please provide a reproducer, if possible using only the ssl module.

  2. zweizeichen commented on Dec 11, 2025

    @zweizeichen

    No reproducer but if it helps: very similar situation here when connecting to valkey with a custom CA cert.

    Image
  3. zweizeichen commented on Dec 12, 2025

    @zweizeichen

    I did some more testing to come up with a minimal repro: seems like rss just keeps growing while the heap stays somewhat constant. I was unable to reproduce it using a simple sync case locally and on the cluster and this started to smell an awful lot like #109534 which might be async-related. Like @HenriBlacksmith's case we're also seeing this in an ASGI app on k8s using the debian trixie base image since the 3.14 release.

    I'll attempt to redeploy this glibc-less and report back.

    Update: the alleged leak is still there even in an alpine container

  4. zweizeichen commented on Dec 15, 2025

    @zweizeichen

    Potentially related: #84904

  5. HenriBlacksmith commented on Dec 15, 2025

    @HenriBlacksmith
    Author

    I applied a fix that improved the memory situation on my other FastAPI app by reducing the calls generated by MSAL.

    I applied a few dependency upgrades on top including a bump of the Python image from 3.14.0-slim to 3.14.2-slim and it caused the memory leak to be worse.

    Though I also need to collect detailed figures about this one as urllib3 version is different in this app because of some version constraints on a library.

    I will try to get more info from memray on this one.

  6. HenriBlacksmith commented on Dec 16, 2025

    @HenriBlacksmith
    Author

    I was able to export new memray profiles from my other application.

    This time the leak is coming from httpx which also relies on ssl package under the hood:

    Image Image Image Image

    Sorry for the cuts on the capture, I am trying to "anonymize" things

  7. zweizeichen commented on Dec 16, 2025

    @zweizeichen

    Thank you! I'm not 100% sure how to interpret these memray outputs to be honest, because if I understand this correctly this shows us where allocations do happen but not necessarily where the leak is (it may be correlated though). This is useful for finding out 'which objects eat up my memory?'. Technically speaking memray will only show us the allocations/leaks the python allocator knows about, but the leak kind of escapes the realm of the allocator by definition. Another layer which might be adding to this is that this might actually be fragmentation instead of leaks. I hope I ruled this out more or less by checking it's not glibc-related by also observing this behaviour on an alpine container, feel free to double-check.

    I'm going to try to find some time to give the minimal (hopefully stdlib only) repro another go and explore analyzing a full heap dump.

  8. HenriBlacksmith commented on Dec 16, 2025

    @HenriBlacksmith
    Author

    I am trying to use valgrind instead of memray to get more details (but I am a bit out of my comfort zone to be honest), it does not report much until k8s restarts the container.

    The command looks like:

    valgrind --tool=memcheck --log-file=/tmp/valgrind.log --trace-children=yes --leak-check=yes --verbose
                  python -m uvicorn app.main:shell --host 0.0.0.0 --log-config logging.yaml

    But nothing really comes out of it

  9. HenriBlacksmith commented on Dec 16, 2025

    @HenriBlacksmith
    Author

    Another layer which might be adding to this is that this might actually be fragmentation instead of leaks. I hope I ruled this out more or less by checking it's not glibc-related by also observing this behaviour on an alpine container, feel free to double-check.

    I just built an Alpine container and ran it and the behavior is similar (based on python:3.14.2-alpine3.23)

    I am running out of ideas, if anyone has guidance to help with investigating this, I would be really happy to test those, if I have extra time, I will try to build a MRO but I am not sure how it can be done

  10. HenriBlacksmith commented on Dec 16, 2025

    @HenriBlacksmith
    Author

    Good news, I might have a MRE, it is using httpx but that's the best I was able to do, I will share the whole code in a repo, including commands to export the graphs.

    Here is the code:

    import httpx
    import asyncio
    
    async def _main() -> None:
        while(1):
            async with httpx.AsyncClient() as client:
                await client.request("GET", "https://example.com/")
            await asyncio.sleep(1)
    
    if __name__ == "__main__":
        asyncio.run(_main())
    FROM astral/uv:0.9.17-python3.14-bookworm AS builder
    ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=0
    
    # Create and set the working directory
    WORKDIR /mro-app
    
    # Install runtime dependencies
    RUN --mount=type=cache,target=/root/.cache/uv \
        --mount=type=bind,source=uv.lock,target=uv.lock \
        --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
        uv sync --frozen --no-install-project --no-dev
    
    # Copy the application code (excluding files in .dockerignore)
    COPY . .
    RUN --mount=type=cache,target=/root/.cache/uv \
            uv sync --frozen --no-dev
    
    # Use an official lightweight Python image with slim variant
    FROM python:3.14.2-slim
    
    # Set environment variables
    # See: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUNBUFFERED
    ENV PYTHONUNBUFFERED=1
    
    # Create and set the working directory
    WORKDIR /mro-app
    
    # Expose the port the app runs on
    EXPOSE 8000
    
    # Copy the application from the builder
    COPY --from=builder /mro-app /mro-app
    
    # Place executables in the environment at the front of the path
    ENV PATH="/mro-app/.venv/bin:$PATH"
    
    # Define the default command to run when the container starts
    CMD ["memray", "run", "--native", "--output", "/tmp/capture.bin", "main.py"]
    [project]
    name = "mro"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.14"
    dependencies = [
        "httpx>=0.28.1",
        "memray>=1.19.1",
        "urllib3>=2.6.2",
    ]

    etc. will build a repo asap with all the other files

    Image Image
  11. HenriBlacksmith commented on Dec 16, 2025

    @HenriBlacksmith
    Author

    Here is the repo, I hope it is an actual valid MRE and not just "normal" usage: https://github.com/HenriBlacksmith/python-ssl-memory-usage-mro/tree/main

  12. HenriBlacksmith commented on Dec 16, 2025

    @HenriBlacksmith
    Author

    And I was finally able to get the native symbols:

    Image

    I will try to compare with older Python versions to see if the sensitivity to Python version holds with the MRE.

    I was able to reproduce on my Mac in a non-Docker environment.

  13. 47 remaining items

  14. added 2 commits that reference this issue on Apr 11, 2026
  15. bedevere-app commented on Apr 17, 2026

    @bedevere-app

    GH-148694 is a backport of this pull request to the 3.14 branch.

  16. added a commit that references this issue on Apr 25, 2026
  17. vstinner commented on Apr 30, 2026

    @vstinner
    Member

    It was decided to Reverting the incremental GC in Python 3.14 and 3.15: see PR gh-148720 for the 3.14 branch. So can we close this issue?

  18. hugovk commented on May 5, 2026

    @hugovk
    Member

    This has been reverted and released in Python 3.14.5 release candidate, please test.

    https://discuss.python.org/t/python-3-14-5-release-candidate/107185/1

    We plan a 3.14.5 final for Friday.

  19. HenriBlacksmith commented on May 19, 2026

    @HenriBlacksmith
    Author

    This has been reverted and released in Python 3.14.5 release candidate, please test.

    https://discuss.python.org/t/python-3-14-5-release-candidate/107185/1

    We plan a 3.14.5 final for Friday.

    I have been testing this on two APIs in our dev environment and it looks memory is stable. Will provide more feedback once it will be in prod.

  20. added a commit that references this issue on May 24, 2026
  21. HenriBlacksmith commented on Jun 2, 2026

    @HenriBlacksmith
    Author

    This has been reverted and released in Python 3.14.5 release candidate, please test.
    https://discuss.python.org/t/python-3-14-5-release-candidate/107185/1
    We plan a 3.14.5 final for Friday.

    I have been testing this on two APIs in our dev environment and it looks memory is stable. Will provide more feedback once it will be in prod.

    It has been running in prod for a week with similar memory profiles as for 3.13.x.

    The issue is resolved from my point of view

  22. miketheman commented on Jun 2, 2026

    @miketheman
    Member

    I can also confirm that the memory footprint is now "stable" since 3.14.5 - and a side benefit has been that the "floor" is lower from 3.13.x

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions