Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ Bug fixes
:class:`~pymongo.asynchronous.cursor.AsyncCursor` to be
``Optional[Mapping[str, Any]]`` instead of ``Optional[bool]``, matching what
the argument accepts at runtime (`PYTHON-6145`_).
- Disabled TLS session reuse on Python 3.11
due to a CPython memory leak bug (`python/cpython#116810 <https://github.com/python/cpython/issues/116810>`_).
The bug was fixed in Python 3.12.7 and Python 3.13.1.
Users of Python 3.12 and 3.13 should upgrade to at least those versions (`PYTHON-6182`_).

.. _PYTHON-5929: https://jira.mongodb.org/browse/PYTHON-5929
.. _PYTHON-6172: https://jira.mongodb.org/browse/PYTHON-6172
.. _PYTHON-6149: https://jira.mongodb.org/browse/PYTHON-6149
.. _PYTHON-5873: https://jira.mongodb.org/browse/PYTHON-5873
.. _PYTHON-6145: https://jira.mongodb.org/browse/PYTHON-6145
.. _PYTHON-6182: https://jira.mongodb.org/browse/PYTHON-6182

Changes in Version 4.19.0 (2026/10/14)
--------------------------------------
Expand Down
6 changes: 5 additions & 1 deletion pymongo/pool_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
_ConnT = TypeVar("_ConnT", bound="_AgnosticBaseConnection[Any]")
_PoolT = TypeVar("_PoolT", bound="_AgnosticBasePool[Any]")

# Reading SSLSocket.session / SSLObject.session leaks a copy of the session on
# every call in CPython 3.11, so disable TLS session reuse on that version.
_TLS_SESSION_REUSE_LEAKS = sys.version_info < (3, 12)


class _ConnectionTelemetryInfo(Protocol):
"""Protocol for connection fields consumed by :class:`~pymongo._telemetry._CommandTelemetry`."""
Expand Down Expand Up @@ -308,7 +312,7 @@ def __init__(
self._pending = 0
self._max_connecting = self.opts.max_connecting
self._ssl_session_cache: Optional[list[Any]] = (
[None] if self.opts._ssl_context is not None else None
[None] if self.opts._ssl_context is not None and not _TLS_SESSION_REUSE_LEAKS else None
)
self._telemetry = _CmapTelemetry(
client_id, address, options._event_listeners, publish=not is_sdam, log=not is_sdam
Expand Down
9 changes: 9 additions & 0 deletions test/asynchronous/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure
from pymongo.hello import HelloCompat
from pymongo.pool_shared import (
_TLS_SESSION_REUSE_LEAKS,
_configured_protocol_interface,
_configured_socket_interface,
_get_ssl_session,
Expand Down Expand Up @@ -923,11 +924,19 @@ async def test_pyopenssl_ignored_in_async(self):
await client.close()

@async_client_context.require_tls
@unittest.skipIf(_TLS_SESSION_REUSE_LEAKS, "TLS session reuse is not supported on Python 3.11")
async def test_pool_has_ssl_session_cache(self):
pool = list(self.client._topology._servers.values())[0].pool
self.assertIsInstance(pool._ssl_session_cache, list)

@async_client_context.require_tls
@unittest.skipUnless(_TLS_SESSION_REUSE_LEAKS, "TLS session reuse is enabled on Python 3.12+")
async def test_pool_has_no_ssl_session_cache_on_python_311(self):
pool = list(self.client._topology._servers.values())[0].pool
self.assertIsNone(pool._ssl_session_cache)

@async_client_context.require_tls
@unittest.skipIf(_TLS_SESSION_REUSE_LEAKS, "TLS session reuse is not supported on Python 3.11")
@unittest.skipUnless(
_IS_SYNC and _HAVE_PYOPENSSL,
"Sync stdlib ssl may return None for session on TLS 1.3; test limited to PyOpenSSL",
Expand Down
9 changes: 9 additions & 0 deletions test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure
from pymongo.hello import HelloCompat
from pymongo.pool_shared import (
_TLS_SESSION_REUSE_LEAKS,
_configured_socket_interface,
_get_ssl_session,
)
Expand Down Expand Up @@ -912,11 +913,19 @@ def test_pyopenssl_ignored_in_async(self):
client.close()

@client_context.require_tls
@unittest.skipIf(_TLS_SESSION_REUSE_LEAKS, "TLS session reuse is not supported on Python 3.11")
def test_pool_has_ssl_session_cache(self):
pool = list(self.client._topology._servers.values())[0].pool
self.assertIsInstance(pool._ssl_session_cache, list)

@client_context.require_tls
@unittest.skipUnless(_TLS_SESSION_REUSE_LEAKS, "TLS session reuse is enabled on Python 3.12+")
def test_pool_has_no_ssl_session_cache_on_python_311(self):
pool = list(self.client._topology._servers.values())[0].pool
self.assertIsNone(pool._ssl_session_cache)

@client_context.require_tls
@unittest.skipIf(_TLS_SESSION_REUSE_LEAKS, "TLS session reuse is not supported on Python 3.11")
@unittest.skipUnless(
_IS_SYNC and _HAVE_PYOPENSSL,
"Sync stdlib ssl may return None for session on TLS 1.3; test limited to PyOpenSSL",
Expand Down
Loading