GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — TLS/SSL asyncio leaks memory · Issue #109534 · python/cpython

TLS/SSL asyncio leaks memory  #109534

New issue
Copy link
New issue
Copy link
Closed as not planned
Closed as not planned
TLS/SSL asyncio leaks memory #109534
Copy link
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirperformancePerformance or resource usagePerformance or resource usagetopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@rojamit
rojamit
opened on Sep 18, 2023
Issue body actions

Bug report

Bug description:

python3.9 without uvloop doesn't leaks memory (or noticeably smaller).
python3.11+ (and others?) leaks memory A LOT under load (with or without uvloop) - up to +2gb per every test!

test commands:
ab -n50000 -c15000 -r https://127.0.0.1/
(apt install apache2-utils)

import asyncio, ssl, uvloop

class HTTP(asyncio.Protocol):
    
    def __init__(self):
        self.transport = None
        
    def connection_made(self, transport):
        self.transport = transport

    def data_received(self, data):
        self.transport.write(
            b'HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n'
        )
        self.transport.close()
        
def make_tls_context():
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    ctx.load_cert_chain('cert.crt', 'cert.key')
    return ctx

tls_context = make_tls_context()
loop = uvloop.new_event_loop()

async def start_server(loop):
    return await loop.create_server(
        HTTP, '127.0.0.1', 443, backlog=65535,
        ssl=tls_context)

loop.run_until_complete(start_server(loop))
loop.run_forever()

CPython versions tested on:

3.9, 3.11, 3.12

Operating systems tested on:

Debian Linux

Linked PRs

  • gh-109534: Asyncio sslproto memleak #113338
  • gh-109534: fix reference leak when SSL handshake fails #114074
  • gh-109534: switch from sock_call to sock_call_ex in sock_send #114311
  • pythongh-109534: close transport if waiter is done #114367
  • [3.12] gh-109534: fix reference leak when SSL handshake fails (GH-114074) #114829
  • [3.11] gh-109534: fix reference leak when SSL handshake fails (GH-114074) #114830
  • pythongh-109534: get rid of bytearray as class member #115643
Reacted by dursk, Yann, Suraj, Tobiasz Kędzierski, Nico Neumann and Daniel Loy
Reacted by Felippe Medeiros, Suraj, Nico Neumann and Oleg A.
Reacted by Sergey Levitin, rojamit, Felippe Medeiros, Suraj, Tobiasz Kędzierski, Nico Neumann and Oleg A.

Activity

Next
  1. rojamit
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Sep 18, 2023
  2. selevit commented on Nov 17, 2023

    @selevit
    selevit
    on Nov 17, 2023
    More actions

    Can confirm that the issue exists - Python 3.11, and uvloop 0.17.0, when trying to set up many client SSL connections that sometimes reconnects - even directly with asyncio.create_connection, and it's cleaned up not properly in some place.

    Sometimes it leaked several megabytes per second for me.

    I was able to track this by attaching to a running python process via gdb, and when it leaked, I put a catchpoint for brk syscalls, and examined 40 sequential breakpoint events, when all were related to /usr/lib/python3.10/ssl.py.

    (gdb) py-bt
    Traceback (most recent call first):
      File "/usr/lib/python3.10/ssl.py", line 917, in read
        v = self._sslobj.read(len)
      <built-in method buffer_updated of uvloop.loop.SSLProtocol object at remote 0xffff758f90c0>
      <built-in method run of _contextvars.Context object at remote 0xffff758c7340>
      File "/home/ubuntu/py-core/env/lib/python3.10/site-packages/aiohttp/web.py", line 544, in run_app
        loop.run_until_complete(main_task)
      File "/home/ubuntu/py-core/server_app.py", line 39, in <module>
        web.run_app(app_factory(), loop=LOOP, host=args.host, port=args.port)
    (gdb)
    

    Also, a gc.get_objects() count was not actually growing, that's why I suppose that this is related a lower-level native implementation.

  3. arhadthedev
    added
    performancePerformance or resource usagePerformance or resource usage
    topic-asyncio
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    on Nov 18, 2023
  4. github-project-automation
    added this to asyncioon Nov 18, 2023
  5. github-project-automation
    moved this to Todo in asyncioon Nov 18, 2023
  6. arhadthedev commented on Nov 18, 2023

    @arhadthedev
    arhadthedev
    on Nov 18, 2023
    Member
    More actions

    @1st1, @asvetlov, @graingert, @gvanrossum, @kumaraditya303 (as asyncio module experts)

  7. gvanrossum commented on Nov 18, 2023

    @gvanrossum
    gvanrossum
    on Nov 18, 2023
    Member
    More actions

    Unfortunately I am really bad at this type of low level stuff. :-( If you suspect a leak in the C accelerator, you can disable it and see if the leak goes away. (Or at least becomes less severe.)

  8. gvanrossum commented on Nov 18, 2023

    @gvanrossum
    gvanrossum
    on Nov 18, 2023
    Member
    More actions

    OTOH if the problem is in the SSL code, we need a different kind of expert.

  9. gvanrossum commented on Nov 18, 2023

    @gvanrossum
    gvanrossum
    on Nov 18, 2023
    Member
    More actions

    Finally please figure out if this is in uvloop or not. If it’s tied to uvloop this is the wrong tracker.

  10. selevit commented on Nov 19, 2023

    @selevit
    selevit
    on Nov 19, 2023
    More actions

    Unfortunately, this bug was reproduced only in our production, and we weren't able to cause it in other environments.

    It's already mitigated, as the root cause was in poor stability of connections.

    I don't think I would be able to reproduce it again in production without uvloop, because it caused a lot of harm for our system.

    But ready to assist and provide more details, if anyone wants to ask me some clarifying questions about it.

    Also, there are several related tickets, and I'm not sure if they are all about uvloop:

    1. asyncio: potential leak of TLS connections #106684 - very similar cpython issue, already closed though.
    2. Memory leak with Python 3.11.2 aio-libs/aiohttp#7252 - also, an already closed, but recent aiohttp issue, they were able to mitigate it somehow, but not sure if it was the same bug.
    Reacted by Yury Zhuravlev
  11. selevit commented on Nov 19, 2023

    @selevit
    selevit
    on Nov 19, 2023
    More actions

    I actually was able to reproduce this leak isolated several days ago, but it leaked for only ~5 megabytes in several hours. I adopted a repro script from this comment to the aiohttp bug mentioned above to be ready for run, and ran it for about 4 hours.

    The initial memory consumption (RSS) had initially stabilised on 258120 bytes, but after several hours increased to ~264000. Hope it's the same bug.

    #!/usr/bin/env python3
    import aiohttp
    import tracemalloc
    import ssl
    import asyncio
    
    def init():
        timeout = aiohttp.ClientTimeout(connect=5, sock_read=5)
        ssl_ctx = ssl._create_unverified_context()
        conn = aiohttp.TCPConnector(ssl=ssl_ctx, enable_cleanup_closed=True)
        session = aiohttp.ClientSession(connector=conn, timeout=timeout, cookie_jar=aiohttp.CookieJar(unsafe=True))
        return session
    
    async def fetch(client):
        try:
            async with client.request('GET', url=f'https://api.pro.coinbase.com/products/BTC-USD/ticker') as r:
                msg = await r.text()
                print(msg)
        except asyncio.CancelledError:
            raise
        except Exception as err:
            print("error", err)
            pass
    
    async def main(): 
        requests = 600
        clients = [init() for _ in range(requests)]
        tracemalloc.start()
        try:
            while True:
                await asyncio.gather(*[fetch(client) for client in clients])
                await asyncio.sleep(5)
        except asyncio.CancelledError:
            pass  # end and clean things up
        finally:
            memory_used = tracemalloc.get_traced_memory()
            snapshot = tracemalloc.take_snapshot()
            stats = snapshot.statistics('lineno')
            for stat in stats[:10]:
                print(stat)
            try:
                await asyncio.gather(*[client.close() for client in clients.values()])
            except:
                pass
    
    asyncio.run(main())
  12. gvanrossum commented on Nov 19, 2023

    @gvanrossum
    gvanrossum
    on Nov 19, 2023
    Member
    More actions

    Could you find a reproducer without aiohttp?

    Reacted by Sergey Levitin
  13. Faolain commented on Nov 27, 2023

    @Faolain
    Faolain
    on Nov 27, 2023
    More actions

    Unsure if this is helpful at all or related but I came across this issue Kludex/uvicorn#2078 in which in the thread it is discussed/concluded that the issue of memory not being released is not isolated to uvicorn but seen in granian/gunicorn/hypercorn and as a result could be interpreter level (apologies for butchering the summary). Thread has some great charts/analysis though it is on the server level, however the different implementations w.r.t granian and uvicorn can help approximate where the issue might be surfacing if it's related. Example repo: https://github.com/Besedo/memory_issue/tree/main by @EBazarov Apologies in advance if it is unrelated and will remove the comment/create one in the right place.

    gi0baro:

    Given everything we stated in the upper replies, I suspect there's no much to do about this. Given the number of frameworks and servers tested, it just seems the Python interpreter is the culprit here, as even if the heap memory gets released, the RSS remains high.

  14. 83 remaining items

  15. gyula-lakatos commented on Jul 2, 2024

    @gyula-lakatos
    gyula-lakatos
    on Jul 2, 2024
    More actions

    This should be highlighted as I (and many others) could've avoided a lot of headache had I not upgraded my applications to the latest Python (or used a different language altogether).

    I can absolutely relate to this because we ended up rewriting the whole app in Rust. One of the main reasons was this bug (and async libs being quite buggy in general). It took me days to debug what's going on and finally landed in here.

  16. fifdee commented on Jul 3, 2024

    @fifdee
    fifdee
    on Jul 3, 2024
    More actions

    In my case MALLOC_TRIM_THRESHOLD_=131072 seems to work. Those vertical lines are project updates. I'm using Python 3.11 and Django 5.0.
    I've not seen memory consumption higher than 450 MB, but I got rid of media passing through Django server, just JSON requests and responses.
    Zrzut ekranu 2024-07-03 123254

    Reacted by Gaurav Kumbhat, Faolain and Oleg A.
  17. nikumar1206 commented on Sep 29, 2024

    @nikumar1206
    nikumar1206
    on Sep 29, 2024
    More actions

    Are there any updates on this issue? Did anyone open up a bug report with the glibc maintainers?

    This issue has made trying to run a long-running Python server nearly impossible.

  18. stalkerg commented on Oct 7, 2024

    @stalkerg
    stalkerg
    on Oct 7, 2024
    More actions

    @nikumar1206 I did attempt a few moments ago https://sourceware.org/pipermail/libc-help/2024-October/006791.html but not sure.

    Reacted by Nikhil Kumar
  19. geraldog commented on Oct 7, 2024

    @geraldog
    geraldog
    on Oct 7, 2024
    More actions

    This is helpful @stalkerg assuming the glibc maintainers actually get to reading the thread they will eventually come up with something, hopefully not a won't-fix

  20. 1st1 commented on Oct 8, 2024

    @1st1
    1st1
    on Oct 8, 2024
    Member
    More actions
    • @fantix
    Reacted by Marcelo Trylesinski and Suraj
  21. CodeToFreedom commented on Feb 21, 2025

    @CodeToFreedom
    CodeToFreedom
    on Feb 21, 2025
    More actions

    Hello,
    we have the same problem. Did anyone find a solution or workaround in the meantime? Many thanks for your efforts.

  22. geraldog commented on Feb 22, 2025

    @geraldog
    geraldog
    on Feb 22, 2025
    More actions

    Hello, we have the same problem. Did anyone find a solution or workaround in the meantime? Many thanks for your efforts.

    Ditching glibc entirely seems to be a solution after all...

  23. CodeToFreedom commented on Feb 22, 2025

    @CodeToFreedom
    CodeToFreedom
    on Feb 22, 2025
    More actions

    @geraldog Many thanks for your reply.
    It looks like a great workaround but to be fully honest I am not familiar enough with the low level package dependencies to completely understand the consequences.
    I assume most python environments (docker containers like f.e. python-3.11-slim etc) and many python packages depend on c support.
    How exactly do you recommend to replace glibc then? is there a compatible package as drop-in replacement that you can recommend? Or do you use other base images to create the runtime environments?

    Again many thanks for your help. I really appreciate it a lot.

  24. geraldog commented on Feb 22, 2025

    @geraldog
    geraldog
    on Feb 22, 2025
    More actions

    How exactly do you recommend to replace glibc then? is there a compatible package as drop-in replacement that you can recommend? Or do you use other base images to create the runtime environments?

    Try an Alpine Linux image. People in this thread have had success in working around this bug with it. It's built around musl libc instead of GNU libc.

  25. Eclips4 commented on Mar 11, 2025

    @Eclips4
    Eclips4
    on Mar 11, 2025
    Member
    More actions

    Hi!
    @geraldog could you please summarize what's happening there and if we should close this issue if the problem is not on CPython's end? Thank you in advance.

    Reacted by Oleg A.
  26. geraldog commented on Mar 11, 2025

    @geraldog
    geraldog
    on Mar 11, 2025
    More actions

    Hi @Eclips4

    This is definitely not a CPython issue and I believe the issue should therefore be closed.

    The observed "memory leak" is actually a glibc quirk or optimization as discovered by @stalkerg where memory allocated is never reclaimed with a free(), keeping the previously allocated memory in a pool for later re-use.

    Some users like me have reported success with setting the env var MALLOC_TRIM_THRESHOLD_ to a low value as a workaround, while other users proved the issue is not reproducible with musl libc based system like Alpine.

    Reacted by Oleg A., Yury Zhuravlev and Jamie Phan
  27. Eclips4 commented on Mar 12, 2025

    @Eclips4
    Eclips4
    on Mar 12, 2025
    Member
    More actions

    Thank you for the answer, @geraldog!
    I'm closing this as not planned, since the problem is not at CPython's end.
    Please ping me and I'll reopen this issue(or maybe it would be better to create a new one, because this one has had quite big discussion..) if you find something on CPython's side related to this issue.

    Thank you all for spending time finding the root cause!

    Reacted by Oleg A.
  28. Eclips4
    closed this as not plannedon Mar 12, 2025
  29. github-project-automation
    moved this from Todo to Done in asyncioon Mar 12, 2025
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

    extension-modulesC modules in the Modules dirC modules in the Modules dirperformancePerformance or resource usagePerformance or resource usagetopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    • asyncio
      Status
      Done
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.