PYTHON-3193 - Add ResourceWarning for unclosed MongoClients in __del__ (#1833)

This commit is contained in:
Noah Stapp 2024-09-09 12:04:23 -04:00 committed by GitHub
parent e683b81bf4
commit 2cca2d9e3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 2 deletions

View File

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

View File

@ -34,6 +34,7 @@ from __future__ import annotations
import contextlib
import os
import warnings
import weakref
from collections import defaultdict
from typing import (
@ -871,6 +872,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]):
)
self._opened = False
self._closed = False
self._init_background()
if _IS_SYNC and connect:
@ -1180,6 +1182,22 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]):
"""
return database.AsyncDatabase(self, name)
def __del__(self) -> None:
"""Check that this AsyncMongoClient has been closed and issue a warning if not."""
try:
if not self._closed:
warnings.warn(
(
f"Unclosed {type(self).__name__} opened at:\n{self._topology_settings._stack}"
f"Call {type(self).__name__}.close() to safely shut down your client and free up resources."
),
ResourceWarning,
stacklevel=2,
source=self,
)
except AttributeError:
pass
def _close_cursor_soon(
self,
cursor_id: int,
@ -1547,6 +1565,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]):
if self._encrypter:
# TODO: PYTHON-1921 Encrypted MongoClients cannot be re-opened.
await self._encrypter.close()
self._closed = True
if not _IS_SYNC:
# Add support for contextlib.aclosing.

View File

@ -82,7 +82,7 @@ class TopologySettings:
self._topology_id = ObjectId()
# Store the allocation traceback to catch unclosed clients in the
# test suite.
self._stack = "".join(traceback.format_stack())
self._stack = "".join(traceback.format_stack()[:-2])
@property
def seeds(self) -> Collection[tuple[str, int]]:

View File

@ -34,6 +34,7 @@ from __future__ import annotations
import contextlib
import os
import warnings
import weakref
from collections import defaultdict
from typing import (
@ -871,6 +872,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
)
self._opened = False
self._closed = False
self._init_background()
if _IS_SYNC and connect:
@ -1180,6 +1182,22 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
"""
return database.Database(self, name)
def __del__(self) -> None:
"""Check that this MongoClient has been closed and issue a warning if not."""
try:
if not self._closed:
warnings.warn(
(
f"Unclosed {type(self).__name__} opened at:\n{self._topology_settings._stack}"
f"Call {type(self).__name__}.close() to safely shut down your client and free up resources."
),
ResourceWarning,
stacklevel=2,
source=self,
)
except AttributeError:
pass
def _close_cursor_soon(
self,
cursor_id: int,
@ -1543,6 +1561,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
if self._encrypter:
# TODO: PYTHON-1921 Encrypted MongoClients cannot be re-opened.
self._encrypter.close()
self._closed = True
if not _IS_SYNC:
# Add support for contextlib.closing.

View File

@ -82,7 +82,7 @@ class TopologySettings:
self._topology_id = ObjectId()
# Store the allocation traceback to catch unclosed clients in the
# test suite.
self._stack = "".join(traceback.format_stack())
self._stack = "".join(traceback.format_stack()[:-2])
@property
def seeds(self) -> Collection[tuple[str, int]]:

View File

@ -95,6 +95,9 @@ filterwarnings = [
"module:please use dns.resolver.Resolver.resolve:DeprecationWarning",
# https://github.com/dateutil/dateutil/issues/1314
"module:datetime.datetime.utc:DeprecationWarning:dateutil",
# TODO: Remove both of these in https://jira.mongodb.org/browse/PYTHON-4731
"ignore:Unclosed AsyncMongoClient*",
"ignore:Unclosed MongoClient*",
]
markers = [
"auth_aws: tests that rely on pymongo-auth-aws",