Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 9, 2024

Conversation

NoahStapp
Copy link
Contributor

No description provided.

@NoahStapp NoahStapp requested a review from blink1073 September 5, 2024 15:44
@blink1073 blink1073 removed the request for review from caseyclements September 5, 2024 17:10
@@ -1173,6 +1175,22 @@ def __getitem__(self, name: str) -> database.AsyncDatabase[_DocumentType]:
"""
return database.AsyncDatabase(self, name)

def __del__(self) -> None:
"""Check that this AsyncMongoClient has been closed and issue a warning if not."""
# TODO: Remove in https://jira.mongodb.org/browse/PYTHON-4731
Copy link
Member

@blink1073 blink1073 Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize we removed MongoClient.__del__. I think we should preserve the 4.8 behavior and change connect=False and remove the __del__ behavior together in 5.0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we want to keep the __del__ warning forever?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, we never had a __del__. Yes, I agree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this comment now.

source=self,
)
if _IS_SYNC and self._opened:
self.close() # type: ignore[unused-coroutine]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't call close() even in the sync api because it might deadlock.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh good point.

try:
if not self._closed:
warnings.warn(
f"Unclosed {self}",
Copy link
Member

@ShaneHarvey ShaneHarvey Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add more information here so users know why/when this started.

Copy link
Contributor Author

@NoahStapp NoahStapp Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of information should be here? Something like:

Unclosed MongoClient. Call MongoClient.close() to safely shut down your client and free up resources.?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should point users to the docs for https://jira.mongodb.org/browse/PYTHON-3606 (still TODO)

Copy link
Contributor Author

@NoahStapp NoahStapp Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to pull that ticket up to 4.9, then? We should do them both in the same release if we want them to be linked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We need good docs or else we're going to be inundated with questions.

@ShaneHarvey
Copy link
Member

@NoahStapp could you also do some history digging into __del__? I remember there was a reason we avoided adding it to client in the past. Something related to reference cycles.

@NoahStapp
Copy link
Contributor Author

@NoahStapp could you also do some history digging into __del__? I remember there was a reason we avoided adding it to client in the past. Something related to reference cycles.

My understanding is that if you try to do any actual cleanup inside __del__, you risk potentially causing issues with the entire reference cycle your object is part of. All of the past tickets I found on this topic relate to attempting to do cleanup: since this version of __del__ is only raising a warning if the client wasn't explicitly closed before garbage collection, we should avoid any issues.

@ShaneHarvey
Copy link
Member

Yes that's one issue but there's another unrelated issue. Adding __del__ to a class changes the way CPython garbage collects the object. In particular if it's part of a reference cycle the GC can be significantly delayed because __del__ is defined. I'm going based on memory so this is a bit fuzzy and CPython could work differently now.

# TODO: Remove in https://jira.mongodb.org/browse/PYTHON-4731
try:
if not self._closed:
if _IS_SYNC:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to remove the await from the error message to reduce the duplication here.

if _IS_SYNC:
msg = (
f"Unclosed {type(self)}. "
f"Call {type(self)}.close() to safely shut down your client and free up resources."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message can/should also include the traceback of where the client was opened via topology_settings._stack.

@@ -1173,6 +1175,22 @@ def __getitem__(self, name: str) -> database.AsyncDatabase[_DocumentType]:
"""
return database.AsyncDatabase(self, name)

def __del__(self) -> None:
"""Check that this AsyncMongoClient has been closed and issue a warning if not."""
# TODO: Remove in https://jira.mongodb.org/browse/PYTHON-4731
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this comment now.

@NoahStapp
Copy link
Contributor Author

Yes that's one issue but there's another unrelated issue. Adding __del__ to a class changes the way CPython garbage collects the object. In particular if it's part of a reference cycle the GC can be significantly delayed because __del__ is defined. I'm going based on memory so this is a bit fuzzy and CPython could work differently now.

I can't find an example of __del__ delaying GC, but I did find this: the garbage collector docs suggest that starting in 3.4, defining __del__ shouldn't affect the GC behavior for that object, even if it is part of a reference cycle.

@ShaneHarvey
Copy link
Member

Thanks for looking into it. Could you?:

  • add changelog entry
  • merge master to get the tests green
  • provide an example of the new warning behavior

@NoahStapp
Copy link
Contributor Author

NoahStapp commented Sep 6, 2024

Here's the current warning:

sys:1: ResourceWarning: Unclosed MongoClient opened at:
  File "/Users/nstapp/Github/mongo-python-driver/scratch.py", line 8, in <module>
    client = MongoClient()
  File "/Users/nstapp/Github/mongo-python-driver/pymongo/synchronous/mongo_client.py", line 847, in __init__
    self._topology_settings = TopologySettings(
  File "/Users/nstapp/Github/mongo-python-driver/pymongo/synchronous/settings.py", line 85, in __init__
    self._stack = "".join(traceback.format_stack())
Call MongoClient.close() to safely shut down your client and free up resources.

@ShaneHarvey
Copy link
Member

What about the "Call {type(self).name}.close() to safely shut down your client and free up resources." part?

@ShaneHarvey
Copy link
Member

Also I wonder if we can remove the last two frames from the trace to make it cleaner.

@NoahStapp
Copy link
Contributor Author

What about the "Call {type(self).name}.close() to safely shut down your client and free up resources." part?

Fixed.

@NoahStapp
Copy link
Contributor Author

Also I wonder if we can remove the last two frames from the trace to make it cleaner.

Return just the first frame? That should always be the line where the client was constructed, right?

@ShaneHarvey
Copy link
Member

I mean

self._stack = "".join(traceback.format_stack()[:-2])

@@ -42,6 +42,8 @@ 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 .
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resources . -> resources.

Let's include an example of the warning here too.

@@ -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.
Here's an example of what the warning looks like:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Here's an example of what the warning looks like: -> For example:

Copy link
Member

@blink1073 blink1073 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@NoahStapp NoahStapp merged commit 2cca2d9 into mongodb:master Sep 9, 2024
34 checks passed
@NoahStapp NoahStapp deleted the PYTHON-3193 branch September 9, 2024 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants