You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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})
Thanks for putting in the time and effort! Unfortunately, I did not really have the time to look into this sooner.
I can confirm sprinkling gc.collect() in strategic locations (non-critical not that hot code paths that get called regularly in long-running processes, e.g. liveness probe endpoint) in the codebase alleviates the issue. It's not pretty but at least a workaround until a potential fix makes it into a 3.14.x release - still better than downgrading to 3.13 :D
Some additional info: 3.13 memory use stable, 3.14.1 grows, 3.14.2 grows, 3.14.2t grows. I initially suspected this would be fixed by #142001 but since it leaks for both the 3.14.1 release and for 3.14.2t, that's not it. It's also should not be the "incremental GC" added to 3.14. Needs additional investigation to find why adding gc.collect() fixes the leak.
I tried to narrow down the issue a bit better. It seems SSL is not required. I'm running the script below. It loads from a local web server (port 8001 without SSL) and the memory still goes up quite high. With 3.14.2, it stabilizes at about 420 MB. Version 3.14.0 stabilizes at about 80 MB (sometimes 60 MB). Bisecting between those two versions shows this commit as the cause for the increased memory use: d1a434f (backport of GH-140262).
I'm still not sure what's going on. With the free-threaded GC (which is quite different), the memory usage is also very high. I think "httpx" is creating reference cycles and the GC is not going a good job to free them. If you run full GC collections frequently enough then memory usage stays low. But if you defer collection too long, memory usage goes much higher. That behavior makes me suspect weak references are involved or maybe finalizers that are resurrecting objects.
Yeah, if we set first gc threshold quite low then memory usage is stable (I tried set_threshold(100)).
Yes, httpx (httpx -> httpcore -> h11) creates cycles for example for sentinel values CLIENT, SERVER and others.
Also, if I call gc_collect_full instead of gc_collect_increment then memory usage grows but not so fast.
For 120 loops gc called about 12 times. First six gc runs do nothing (because of work_to_do is low). Maybe we can't break all cycles with one pass?
I was wrong about SSL not being involved. Here is a memray flame graph. Nearly all the memory is allocated from load_verify_locations(), which is implemented in the _ssl.c module, it seems. Given that explicitly calling gc.collect() fixes the memory usage, I would suspect it's not memory leak in the _ssl.c code but due to reference cycles keeping the default context alive.
Edit: passing a context to the verify parameter drastically reduces the memory usage, e.g.
ssl_ctx = ssl.create_default_context()
for i in range(2000):
async with httpx.AsyncClient(verify=ssl_ctx) as client:
resp = await client.get(URL)
Good detective work! If I break that cycle, the high memory usage goes away. E.g. add this line at the end of the "with" block: resp.stream = None. It would be good if httpx would break that cycle when the "with" block is exited. However, we should also try to fix the GC so that these cycles are cleaned more quickly.
Reacted by Sergey Miryanov, Gregory P. Smith and Savio Mak
I don't know if this is an accurate minimization of the test case but it does use more memory in 3.14 vs 3.13 (and more yet in 3.14.2). It's just reference cycles that contain the value from ssl.create_default_context(). To me this looks like a case of relatively few objects involved in a reference cycle keeping quite a bit of memory in use. A single value from create_default_context() uses 800 kB, at least on my machine. So it doesn't take many Response->Stream cycles to chew up a lot of memory.
Another piece of info: the peak memory used by this script is going to be quite dependant on the malloc being used by the SSL library. On my machine, running with LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 causes the RSS to be quite a bit lower compared to using the default malloc (my guess is the default is GNU libc's malloc). Also measuring RSS is tricky. For Linux it would be more accurate to use the following to compute RSS (more expensive to compute but more accurate of actual resistent-set-size).
I found some (other) possible leaks. I don't know if they are directly related or not, but some context objects may leak in some cases. I don't know if any of the involved libraries actually set one of the attributes, and possibly create some cycles, but I there were some refleaks.
With the following patch on main, there would be reference leaks. With my patch, those don't leak anymore:
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index ebdf5455163..80df9c25d2c 100644
--- a/Lib/test/test_ssl.py+++ b/Lib/test/test_ssl.py@@ -57,6 +57,10 @@
CAN_GET_SELECTED_OPENSSL_SIGALG = ssl.OPENSSL_VERSION_INFO >= (3, 5)
PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS')
+HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename')+requires_keylog = unittest.skipUnless(+ HAS_KEYLOG, 'test requires OpenSSL 1.1.1 with keylog callback')+
PROTOCOL_TO_TLS_VERSION = {}
for proto, ver in (
("PROTOCOL_SSLv3", "SSLv3"),
@@ -265,33 +269,69 @@ def utc_offset(): #NOTE: ignore issues like #1647654
)
-def test_wrap_socket(sock, *,- cert_reqs=ssl.CERT_NONE, ca_certs=None,- ciphers=None, ciphersuites=None,- min_version=None, max_version=None,- certfile=None, keyfile=None,- **kwargs):- if not kwargs.get("server_side"):- kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME- context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)- else:+def make_test_context(+ *,+ server_side=False,+ check_hostname=None,+ cert_reqs=ssl.CERT_NONE,+ ca_certs=None, certfile=None, keyfile=None,+ ciphers=None, ciphersuites=None,+ min_version=None, max_version=None,+):+ if server_side:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
- if cert_reqs is not None:+ else:+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)++ if check_hostname is None:
if cert_reqs == ssl.CERT_NONE:
context.check_hostname = False
+ else:+ context.check_hostname = check_hostname++ if cert_reqs is not None:
context.verify_mode = cert_reqs
+
if ca_certs is not None:
context.load_verify_locations(ca_certs)
if certfile is not None or keyfile is not None:
context.load_cert_chain(certfile, keyfile)
+
if ciphers is not None:
context.set_ciphers(ciphers)
if ciphersuites is not None:
context.set_ciphersuites(ciphersuites)
+
if min_version is not None:
context.minimum_version = min_version
if max_version is not None:
context.maximum_version = max_version
++ return context+++def test_wrap_socket(+ sock,+ *,+ server_side=False,+ check_hostname=None,+ cert_reqs=ssl.CERT_NONE,+ ca_certs=None, certfile=None, keyfile=None,+ ciphers=None, ciphersuites=None,+ min_version=None, max_version=None,+ **kwargs+):+ context = make_test_context(+ server_side=server_side,+ check_hostname=check_hostname,+ cert_reqs=cert_reqs,+ ca_certs=ca_certs, certfile=certfile, keyfile=keyfile,+ ciphers=ciphers, ciphersuites=ciphersuites,+ min_version=min_version, max_version=max_version,+ )+ kwargs["server_side"] = server_side+ if not server_side:+ kwargs.setdefault("server_hostname", SIGNED_CERTFILE_HOSTNAME)
return context.wrap_socket(sock, **kwargs)
@@ -1730,6 +1770,39 @@ def test_num_tickest(self):
with self.assertRaises(ValueError):
ctx.num_tickets = 1
+ @support.cpython_only+ def test_refcycle_msg_callback(self):+ # See https://github.com/python/cpython/issues/142516.+ ctx = make_test_context()+ def msg_callback(*args, _=ctx, **kwargs): ...+ ctx._msg_callback = msg_callback++ @support.cpython_only+ @requires_keylog+ def test_refcycle_keylog_filename(self):+ # See https://github.com/python/cpython/issues/142516.+ self.addCleanup(os_helper.unlink, os_helper.TESTFN)+ ctx = make_test_context()+ class KeylogFilename(str): ...+ ctx.keylog_filename = KeylogFilename(os_helper.TESTFN)+ ctx.keylog_filename._ = ctx++ @support.cpython_only+ @unittest.skipUnless(ssl.HAS_PSK, 'requires TLS-PSK')+ def test_refcycle_psk_client_callback(self):+ # See https://github.com/python/cpython/issues/142516.+ ctx = make_test_context()+ def psk_client_callback(*args, _=ctx, **kwargs): ...+ ctx.set_psk_client_callback(psk_client_callback)++ @support.cpython_only+ @unittest.skipUnless(ssl.HAS_PSK, 'requires TLS-PSK')+ def test_refcycle_psk_server_callback(self):+ # See https://github.com/python/cpython/issues/142516.+ ctx = make_test_context(server_side=True)+ def psk_server_callback(*args, _=ctx, **kwargs): ...+ ctx.set_psk_server_callback(psk_server_callback)+
class SSLErrorTests(unittest.TestCase):
@@ -5163,10 +5236,6 @@ def test_internal_chain_server(self):
self.assertEqual(res, b'\x02\n')
-HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename')-requires_keylog = unittest.skipUnless(- HAS_KEYLOG, 'test requires OpenSSL 1.1.1 with keylog callback')-
class TestSSLDebug(unittest.TestCase):
def keylog_lines(self, fname=os_helper.TESTFN):
@nascheme By the way, I found out that the SSL socket object holds some weak references to some other objects (that is, holds the result of PyWeakref_NewRef). Should these pointers be visited? AFAIK, there is no need to since these are weak references but may it's needed? I wasn't able to come up with a reproducer solely relying on SSL and that would actually call traverse.
In addition, with Python 3.14.2 (release build provided by uv), your test script with Cell() has a constant RSS on my side (it stays at 36Mb). Same for Victor's script. OTOH, on a DEBUG build with main, it's not constant and sometimes grows and sometimes not. With Victor's script, even if I uncomment gc.collect(), on a debug build on main, it still leaks!
EDIT: I think the issue stems from the fact that I was using an ASAN DEBUG build.
Ok, so I actually dig into this problem a bit more, especially in the "bad" functions. I found out that when we load certificates, we also create lots of small lists and small tuples and lots of nested tuples. I don't know if this could add some burden to the GC. We have a lot of list->tuple conversions and we kinda construct a tree as well (this is _create_tuple_for_X509_NAME).
I also found some other leaks at import time but it's a separate bug. To help investigating the issue, I'm also considering rewriting the helper functions (e.g., _decode_certificate is quite hard to follow and maybe I missed something).
We hit this issue after upgrading some of our applications to Python 3.14.
What made it worse for us is that we had long running applications that were frequently creating HTTP clients:
using the Prometheus client and push_to_gateway periodically. Which is likely not meant to be used like that as it creates clients, thus SSL contexts for every call.
re-creating httpx clients over and over, which was also creating SSL contexts.
Avoiding creating such HTTP clients solved the memory leaks we were observing.
While this is not going to help solve the actual issue here, I just thought about sharing this in case it might help others.
Memray was very useful to troubleshoot this and figure out where the HTTP client creations were coming from.
@youribonnaffe Thank you for that information, it is helpful I think for CPython maintainers to help prioritize bugfixes and whether to backport them into older releases.
This kind of scenario is what concerned me about the GC threshold change in GH-140262, which I strongly suggested should be reverted in a 3.14.x bugfix release by GH-142001. My reasoning is as follows: Python is often used for long running services running in VMs or containers with memory usage limits. If a bugfix release of Python causes some of those services to suddenly start using multiple times more memory, I think that's quite serious. It likely means these services will be killed (e.g. by the OOM mechanism in Linux) and the service will fail. To me, that seems much worse than losing some performance due to more frequent GC collections.
The GH-142001 only affects programs that consume a lot of memory due to garbage reference cycles. Those are very much a minority of all Python programs and so perhaps this is not too major of a problem. However, this garbage cycle in httpx with the SSL context in one example. We can fix it by avoiding the cycle (or explicitly breaking it). But there are likely many more programs that also create garbage cycles and would be affected by this.
Reacted by HenriBlacksmith, Savio Mak and Julien Duponchelle
With the ssl specific PRs merged above, where do we sit with regards to the need for #142001 ?
The ssl context issue is only an example of where this GC collection threshold change causes the problem. Any time you have reference cycles that hold on to a significant amount of memory without having a large number of container objects, you will see the same issue. I.e. sporadic higher memory usage.
Summarizing Sergey's` tests, Python <= 3.14.1 will break cycles after about 1000 new container objects. Versions >= 3.14.2 will break cycles after about 4600 new container objects. However, it's not very predicable about how many new container objects it takes before it actually looks for cycles.
If your program doesn't create cycles or those cycles don't consume a lot of memory relative to the number of container objects in them, the 3.14.2 change doesn't hurt. It even gives a performance boost, since the GC runs less often.
We hit this issue after upgrading some of our applications to Python 3.14.
What made it worse for us is that we had long running applications that were frequently creating HTTP clients:
using the Prometheus client and push_to_gateway periodically. Which is likely not meant to be used like that as it creates clients, thus SSL contexts for every call.
re-creating httpx clients over and over, which was also creating SSL contexts.
Avoiding creating such HTTP clients solved the memory leaks we were observing. While this is not going to help solve the actual issue here, I just thought about sharing this in case it might help others.
Memray was very useful to troubleshoot this and figure out where the HTTP client creations were coming from.
FWIW: I ran into similar issues while using the same prometheus client and also push gateways. However, we don't use ssl for this, so in my case the memory did not leak from ssl contexts. Instead it was leaked from the handlers created by this package.
As a quick experiment I cached the output of default_handler and the memory issues went away quickly. In the end I chose the least invasive change which is to just simply run gc.collect() right after the call to pushadd_to_gateway(), for now.
NB: also in python 3.13 there is more memory being used than "strictly needed" - but that was not an issue so far. A machine with 16 GB RAM running multiple background tasks (25+) pushing metrics it ran out of memory within a day. With 3.13 it never got this bad: since this only hit our staging environment I don't have data for more than a week; those jobs are redeployed and start memory usage from scratch, so all I can say is that so far it has never exceeded that 16 GB of RAM in a week.
Reacted by HenriBlacksmith, Alex Poone and Kashun YOSHIDA
Two suggestions for testing: add explicit gc.collect() calls, if this keeps memory usage in check, it basically confirms it is an issue with cyclic garbage not being freed soon enough. You can also try apply the patch from #142001 which would confirm the same. If you are using httpx, make sure you have this fix in your version: encode/httpx#3733 . That might not be the only reference cycle though and if there are others, 3.14.2+ will likely use more RAM before breaking those cycles.
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
Reacted by Hugo van Kemenade, Sergey Miryanov, Cody Maloney, HenriBlacksmith, Cristian M, Gregory P. Smith, Felix Fanghaenel, 93578237, Victor Stinner and Kuthair Habboush
Activity
HenriBlacksmith commentedon Dec 17, 2025
I tested the same program under Python
3.13.11+debugand here is the memory graph:The pattern shows something like GC is happening
I updated the MRE repo to reflect my tests, no need to use Docker, just running those two programs on my system shows differences
vstinner commentedon Dec 17, 2025
Do you also see the leak on Python 3.14 if you call
gc.collect()time to time?HenriBlacksmith commentedon Dec 17, 2025
Here is the same code with
3.14.2+debugrunning the same number of iterations (the other graph was on a shorter time frame):I will see to run with a call to
gc.collect()every iterationHenriBlacksmith commentedon Dec 17, 2025
Here are the results for the code above:
vstinner commentedon Dec 17, 2025
So if you call explicitly
gc.collect(), there is no more leak?It seems like Python 3.14 GC behaves differently.
cc @nascheme @markshannon
sergey-miryanov commentedon Dec 17, 2025
I would like to look into this if no one mind.
vstinner commentedon Dec 17, 2025
Reproducer modified to use
www.google.comand display the RSS memory usage at each iteration:Output, the script leaks around 1 MiB per iteration:
If I uncomment
gc.collect()line, the memory usage is basically stable:[-]Observed memory leak in ssl library[/-][+]Observed memory leak in ssl library: Python 3.14 GC issue[/+]zweizeichen commentedon Dec 17, 2025
Thanks for putting in the time and effort! Unfortunately, I did not really have the time to look into this sooner.
I can confirm sprinkling
gc.collect()in strategic locations (non-critical not that hot code paths that get called regularly in long-running processes, e.g. liveness probe endpoint) in the codebase alleviates the issue. It's not pretty but at least a workaround until a potential fix makes it into a 3.14.x release - still better than downgrading to 3.13 :Dnascheme commentedon Dec 17, 2025
Some additional info: 3.13 memory use stable, 3.14.1 grows, 3.14.2 grows, 3.14.2t grows. I initially suspected this would be fixed by #142001 but since it leaks for both the 3.14.1 release and for 3.14.2t, that's not it. It's also should not be the "incremental GC" added to 3.14. Needs additional investigation to find why adding
gc.collect()fixes the leak.sergey-miryanov commentedon Dec 17, 2025
@nascheme On main (1eddef8) I see that rss only grows, on my gc-fix branch it looks like a saw https://gist.github.com/sergey-miryanov/8e8d11311d154ab2c7656b8e546e64aa
sergey-miryanov commentedon Dec 17, 2025
Following doesn't leak on main (92243dc):
sergey-miryanov commentedon Dec 18, 2025
@nascheme Memory grows on 3.13.1 and 3.13.9, but it stabilizes at some point:
Details
nascheme commentedon Dec 18, 2025
I tried to narrow down the issue a bit better. It seems SSL is not required. I'm running the script below. It loads from a local web server (port 8001 without SSL) and the memory still goes up quite high. With 3.14.2, it stabilizes at about 420 MB. Version 3.14.0 stabilizes at about 80 MB (sometimes 60 MB). Bisecting between those two versions shows this commit as the cause for the increased memory use: d1a434f (backport of GH-140262).
I'm still not sure what's going on. With the free-threaded GC (which is quite different), the memory usage is also very high. I think "httpx" is creating reference cycles and the GC is not going a good job to free them. If you run full GC collections frequently enough then memory usage stays low. But if you defer collection too long, memory usage goes much higher. That behavior makes me suspect weak references are involved or maybe finalizers that are resurrecting objects.
sergey-miryanov commentedon Dec 18, 2025
Yeah, if we set first gc threshold quite low then memory usage is stable (I tried
set_threshold(100)).Yes, httpx (httpx -> httpcore -> h11) creates cycles for example for sentinel values
CLIENT,SERVERand others.Also, if I call
gc_collect_fullinstead ofgc_collect_incrementthen memory usage grows but not so fast.For 120 loops gc called about 12 times. First six gc runs do nothing (because of work_to_do is low). Maybe we can't break all cycles with one pass?
sergey-miryanov commentedon Dec 18, 2025
One example of the cycle:
Response -> BoundAsyncStream -> Response -> ...
nascheme commentedon Dec 18, 2025
I was wrong about SSL not being involved. Here is a memray flame graph. Nearly all the memory is allocated from
load_verify_locations(), which is implemented in the_ssl.cmodule, it seems. Given that explicitly callinggc.collect()fixes the memory usage, I would suspect it's not memory leak in the_ssl.ccode but due to reference cycles keeping the default context alive.Edit: passing a context to the
verifyparameter drastically reduces the memory usage, e.g.nascheme commentedon Dec 18, 2025
Good detective work! If I break that cycle, the high memory usage goes away. E.g. add this line at the end of the "with" block:
resp.stream = None. It would be good if httpx would break that cycle when the "with" block is exited. However, we should also try to fix the GC so that these cycles are cleaned more quickly.nascheme commentedon Dec 18, 2025
I don't know if this is an accurate minimization of the test case but it does use more memory in 3.14 vs 3.13 (and more yet in 3.14.2). It's just reference cycles that contain the value from
ssl.create_default_context(). To me this looks like a case of relatively few objects involved in a reference cycle keeping quite a bit of memory in use. A single value fromcreate_default_context()uses 800 kB, at least on my machine. So it doesn't take many Response->Stream cycles to chew up a lot of memory.nascheme commentedon Dec 19, 2025
Another piece of info: the peak memory used by this script is going to be quite dependant on the malloc being used by the SSL library. On my machine, running with
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2causes the RSS to be quite a bit lower compared to using the default malloc (my guess is the default is GNU libc's malloc). Also measuring RSS is tricky. For Linux it would be more accurate to use the following to compute RSS (more expensive to compute but more accurate of actual resistent-set-size).HenriBlacksmith commentedon Jan 5, 2026
If understand correctly, the patch referenced above (#142001) should improve the GC situation?
Is this planned for the next patch release ?
Possible fix for python#142516
pythongh-142516: fix reference leaks in `ssl` objects
pythongh-142516: fix reference leaks on `ssl.SSLContext` objects
pythongh-142516: fix reference leaks in `ssl.SSLContext` objects
picnixz commentedon Jan 11, 2026
I found some (other) possible leaks. I don't know if they are directly related or not, but some context objects may leak in some cases. I don't know if any of the involved libraries actually set one of the attributes, and possibly create some cycles, but I there were some refleaks.
With the following patch on
main, there would be reference leaks. With my patch, those don't leak anymore:picnixz commentedon Jan 11, 2026
@nascheme By the way, I found out that the SSL socket object holds some weak references to some other objects (that is, holds the result of
PyWeakref_NewRef). Should these pointers be visited? AFAIK, there is no need to since these are weak references but may it's needed? I wasn't able to come up with a reproducer solely relying on SSL and that would actually calltraverse.picnixz commentedon Jan 11, 2026
In addition, with Python 3.14.2 (release build provided by uv), your test script with
Cell()has a constant RSS on my side (it stays at 36Mb). Same for Victor's script. OTOH, on a DEBUG build with main, it's not constant and sometimes grows and sometimes not. With Victor's script, even if I uncommentgc.collect(), on a debug build on main, it still leaks!EDIT: I think the issue stems from the fact that I was using an ASAN DEBUG build.
picnixz commentedon Jan 11, 2026
Ok, so I actually dig into this problem a bit more, especially in the "bad" functions. I found out that when we load certificates, we also create lots of small lists and small tuples and lots of nested tuples. I don't know if this could add some burden to the GC. We have a lot of list->tuple conversions and we kinda construct a tree as well (this is
_create_tuple_for_X509_NAME).I also found some other leaks at import time but it's a separate bug. To help investigating the issue, I'm also considering rewriting the helper functions (e.g.,
_decode_certificateis quite hard to follow and maybe I missed something).youribonnaffe commentedon Jan 14, 2026
We hit this issue after upgrading some of our applications to Python 3.14.
What made it worse for us is that we had long running applications that were frequently creating HTTP clients:
push_to_gatewayperiodically. Which is likely not meant to be used like that as it creates clients, thus SSL contexts for every call.Avoiding creating such HTTP clients solved the memory leaks we were observing.
While this is not going to help solve the actual issue here, I just thought about sharing this in case it might help others.
Memray was very useful to troubleshoot this and figure out where the HTTP client creations were coming from.
nascheme commentedon Jan 14, 2026
@youribonnaffe Thank you for that information, it is helpful I think for CPython maintainers to help prioritize bugfixes and whether to backport them into older releases.
This kind of scenario is what concerned me about the GC threshold change in GH-140262, which I strongly suggested should be reverted in a 3.14.x bugfix release by GH-142001. My reasoning is as follows: Python is often used for long running services running in VMs or containers with memory usage limits. If a bugfix release of Python causes some of those services to suddenly start using multiple times more memory, I think that's quite serious. It likely means these services will be killed (e.g. by the OOM mechanism in Linux) and the service will fail. To me, that seems much worse than losing some performance due to more frequent GC collections.
The GH-142001 only affects programs that consume a lot of memory due to garbage reference cycles. Those are very much a minority of all Python programs and so perhaps this is not too major of a problem. However, this garbage cycle in httpx with the SSL context in one example. We can fix it by avoiding the cycle (or explicitly breaking it). But there are likely many more programs that also create garbage cycles and would be affected by this.
HenriBlacksmith commentedon Feb 5, 2026
@nascheme @sergey-miryanov, is there anything in 3.14.3 that could help with GC of reference cycles?
(I mean should I try it or nothing specific was merged related to that?)
sergey-miryanov commentedon Feb 5, 2026
@HenriBlacksmith AFAIK, 3.14.3 has no related changes to this problem. We are still waiting some feedback in GH-142001.
gh-142516: fix reference leaks in `ssl.SSLContext` objects (#143685)
[3.14] pythongh-142516: fix reference leaks in `ssl.SSLContext` objec…
[3.14] gh-142516: fix reference leaks in `ssl.SSLContext` objects (GH…
gpshead commentedon Feb 21, 2026
With the ssl specific PRs merged above, where do we sit with regards to the need for #142001 ?
nascheme commentedon Feb 21, 2026
The ssl context issue is only an example of where this GC collection threshold change causes the problem. Any time you have reference cycles that hold on to a significant amount of memory without having a large number of container objects, you will see the same issue. I.e. sporadic higher memory usage.
Summarizing Sergey's` tests, Python <= 3.14.1 will break cycles after about 1000 new container objects. Versions >= 3.14.2 will break cycles after about 4600 new container objects. However, it's not very predicable about how many new container objects it takes before it actually looks for cycles.
If your program doesn't create cycles or those cycles don't consume a lot of memory relative to the number of container objects in them, the 3.14.2 change doesn't hurt. It even gives a performance boost, since the GC runs less often.
pythongh-142516: fix reference leaks in `ssl.SSLContext` objects (pyt…
sergey-miryanov commentedon Mar 10, 2026
JFYI, reproducer without ssl module:
For 3.14 and main memory usage grows to
5_687_720.0 kB.Add GC workaround for CPython 3.14 memory leak in CloudFunctionsSandbox
cpoppema commentedon Mar 18, 2026
FWIW: I ran into similar issues while using the same prometheus client and also push gateways. However, we don't use ssl for this, so in my case the memory did not leak from ssl contexts. Instead it was leaked from the handlers created by this package.
pushadd_to_gateway - without a
handlerarg!_use_gateway:
The default_handler is in fact not a real handler: it creates one on the fly:
As a quick experiment I cached the output of default_handler and the memory issues went away quickly. In the end I chose the least invasive change which is to just simply run
gc.collect()right after the call topushadd_to_gateway(), for now.NB: also in python 3.13 there is more memory being used than "strictly needed" - but that was not an issue so far. A machine with 16 GB RAM running multiple background tasks (25+) pushing metrics it ran out of memory within a day. With 3.13 it never got this bad: since this only hit our staging environment I don't have data for more than a week; those jobs are redeployed and start memory usage from scratch, so all I can say is that so far it has never exceeded that 16 GB of RAM in a week.
nascheme commentedon Apr 10, 2026
Two suggestions for testing: add explicit
gc.collect()calls, if this keeps memory usage in check, it basically confirms it is an issue with cyclic garbage not being freed soon enough. You can also try apply the patch from #142001 which would confirm the same. If you are using httpx, make sure you have this fix in your version: encode/httpx#3733 . That might not be the only reference cycle though and if there are others, 3.14.2+ will likely use more RAM before breaking those cycles.[3.13] pythongh-142516: fix reference leaks in `ssl.SSLContext` objec…
[3.14] pythongh-142516: fix reference leaks in `ssl.SSLContext` objec…
bedevere-app commentedon Apr 17, 2026
GH-148694 is a backport of this pull request to the 3.14 branch.
pythongh-142516: fix reference leaks in `ssl.SSLContext` objects (pyt…
vstinner commentedon Apr 30, 2026
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?
hugovk commentedon May 5, 2026
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.
HenriBlacksmith commentedon May 19, 2026
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.
[3.13] gh-142516: fix reference leaks in `ssl.SSLContext` objects (GH…
fix: guard FDCapture.snap() against prematurely closed tmpfile (pytes…
fix: guard FDCapture.snap() against prematurely closed tmpfile (pytes…
fix: guard FDCapture.snap() against prematurely closed tmpfile (pytes…
fix: guard FDCapture.snap() against prematurely closed tmpfile (pytes…
HenriBlacksmith commentedon Jun 2, 2026
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
miketheman commentedon Jun 2, 2026
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