Skip to content

Commit 2cca2d9

Browse files
authored
PYTHON-3193 - Add ResourceWarning for unclosed MongoClients in __del__ (#1833)
1 parent e683b81 commit 2cca2d9

File tree

6 files changed

+53
-2
lines changed

6 files changed

+53
-2
lines changed

doc/changelog.rst

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ PyMongo 4.9 brings a number of improvements including:
4242
- Fixed a bug where PyMongo would raise ``InvalidBSON: date value out of range``
4343
when using :attr:`~bson.codec_options.DatetimeConversion.DATETIME_CLAMP` or
4444
:attr:`~bson.codec_options.DatetimeConversion.DATETIME_AUTO` with a non-UTC timezone.
45+
- Added a warning to unclosed MongoClient instances
46+
telling users to explicitly close clients when finished with them to avoid leaking resources.
47+
For example:
48+
49+
.. code-block::
50+
51+
sys:1: ResourceWarning: Unclosed MongoClient opened at:
52+
File "/Users/<user>/my_file.py", line 8, in <module>``
53+
client = MongoClient()
54+
Call MongoClient.close() to safely shut down your client and free up resources.
4555
- The default value for ``connect`` in ``MongoClient`` is changed to ``False`` when running on
4656
unction-as-a-service (FaaS) like AWS Lambda, Google Cloud Functions, and Microsoft Azure Functions.
4757
On some FaaS systems, there is a ``fork()`` operation at function

pymongo/asynchronous/mongo_client.py

+19
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import contextlib
3636
import os
37+
import warnings
3738
import weakref
3839
from collections import defaultdict
3940
from typing import (
@@ -871,6 +872,7 @@ def __init__(
871872
)
872873

873874
self._opened = False
875+
self._closed = False
874876
self._init_background()
875877

876878
if _IS_SYNC and connect:
@@ -1180,6 +1182,22 @@ def __getitem__(self, name: str) -> database.AsyncDatabase[_DocumentType]:
11801182
"""
11811183
return database.AsyncDatabase(self, name)
11821184

1185+
def __del__(self) -> None:
1186+
"""Check that this AsyncMongoClient has been closed and issue a warning if not."""
1187+
try:
1188+
if not self._closed:
1189+
warnings.warn(
1190+
(
1191+
f"Unclosed {type(self).__name__} opened at:\n{self._topology_settings._stack}"
1192+
f"Call {type(self).__name__}.close() to safely shut down your client and free up resources."
1193+
),
1194+
ResourceWarning,
1195+
stacklevel=2,
1196+
source=self,
1197+
)
1198+
except AttributeError:
1199+
pass
1200+
11831201
def _close_cursor_soon(
11841202
self,
11851203
cursor_id: int,
@@ -1547,6 +1565,7 @@ async def close(self) -> None:
15471565
if self._encrypter:
15481566
# TODO: PYTHON-1921 Encrypted MongoClients cannot be re-opened.
15491567
await self._encrypter.close()
1568+
self._closed = True
15501569

15511570
if not _IS_SYNC:
15521571
# Add support for contextlib.aclosing.

pymongo/asynchronous/settings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def __init__(
8282
self._topology_id = ObjectId()
8383
# Store the allocation traceback to catch unclosed clients in the
8484
# test suite.
85-
self._stack = "".join(traceback.format_stack())
85+
self._stack = "".join(traceback.format_stack()[:-2])
8686

8787
@property
8888
def seeds(self) -> Collection[tuple[str, int]]:

pymongo/synchronous/mongo_client.py

+19
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import contextlib
3636
import os
37+
import warnings
3738
import weakref
3839
from collections import defaultdict
3940
from typing import (
@@ -871,6 +872,7 @@ def __init__(
871872
)
872873

873874
self._opened = False
875+
self._closed = False
874876
self._init_background()
875877

876878
if _IS_SYNC and connect:
@@ -1180,6 +1182,22 @@ def __getitem__(self, name: str) -> database.Database[_DocumentType]:
11801182
"""
11811183
return database.Database(self, name)
11821184

1185+
def __del__(self) -> None:
1186+
"""Check that this MongoClient has been closed and issue a warning if not."""
1187+
try:
1188+
if not self._closed:
1189+
warnings.warn(
1190+
(
1191+
f"Unclosed {type(self).__name__} opened at:\n{self._topology_settings._stack}"
1192+
f"Call {type(self).__name__}.close() to safely shut down your client and free up resources."
1193+
),
1194+
ResourceWarning,
1195+
stacklevel=2,
1196+
source=self,
1197+
)
1198+
except AttributeError:
1199+
pass
1200+
11831201
def _close_cursor_soon(
11841202
self,
11851203
cursor_id: int,
@@ -1543,6 +1561,7 @@ def close(self) -> None:
15431561
if self._encrypter:
15441562
# TODO: PYTHON-1921 Encrypted MongoClients cannot be re-opened.
15451563
self._encrypter.close()
1564+
self._closed = True
15461565

15471566
if not _IS_SYNC:
15481567
# Add support for contextlib.closing.

pymongo/synchronous/settings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def __init__(
8282
self._topology_id = ObjectId()
8383
# Store the allocation traceback to catch unclosed clients in the
8484
# test suite.
85-
self._stack = "".join(traceback.format_stack())
85+
self._stack = "".join(traceback.format_stack()[:-2])
8686

8787
@property
8888
def seeds(self) -> Collection[tuple[str, int]]:

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ filterwarnings = [
9595
"module:please use dns.resolver.Resolver.resolve:DeprecationWarning",
9696
# https://github.com/dateutil/dateutil/issues/1314
9797
"module:datetime.datetime.utc:DeprecationWarning:dateutil",
98+
# TODO: Remove both of these in https://jira.mongodb.org/browse/PYTHON-4731
99+
"ignore:Unclosed AsyncMongoClient*",
100+
"ignore:Unclosed MongoClient*",
98101
]
99102
markers = [
100103
"auth_aws: tests that rely on pymongo-auth-aws",

0 commit comments

Comments
 (0)