gh-148428: Fix file handle leak in pulldom.parse() - #148437
Conversation
When pulldom.parse() is called with a filename, the opened file handle is never closed. Add close(), context manager support, and __del__ with ResourceWarning to DOMEventStream. Update clear() to close owned streams.
Keep the existing test_parse unchanged; context manager behavior is covered by the new dedicated tests.
Remove test_context_manager_closes_file (covered by test_close_is_idempotent) and revert the unrelated modification to test_parse.
Use handler.close() instead of handler.stream.close() in addCleanup so the DOMEventStream is properly closed before GC triggers __del__.
|
This PR is stale because it has been open for 30 days with no activity. |
serhiy-storchaka
left a comment
There was a problem hiding this comment.
This is a good approach.
Note that __enter__ and __exit__ are not strictly needed -- contextlib.closing() works with any object having close(). They add convenience, but if there are more that one way to finish the object (clear() and close()), their semantic is ambiguous.
On other hand, why do you need close() if there is clear()? There are two variants of simplification:
- Close the file in
clear(), removeclose(), and useclear()in__exit__(). - Close the file in
clear(), makeclose()an alias ofclear(), remove__enter__and__exit__.
The example in the documentation, which the issue quotes as the reason users hit this, is not updated.
The file object opened by parse() already emits its own ResourceWarning when collected unclosed, so DOMEventStream.__del__ is not needed. Rewrite test_context_manager_does_not_close_user_stream to actually use the context manager and pulldom.parse(), and drop comments that repeat the test names.
Per review: close() is removed, clear() now closes the stream when it was opened by parse() (and is safe to call more than once), and the context manager calls clear() on exit. Since minidom's _do_pulldom_parse() already calls clear(), this also fixes the file handle leak in xml.dom.minidom.parse() with a custom parser. Also update the leaking example in the documentation to use the with statement.
The ambiguity is a valid point, I have cleaned up |
Documentation build overview
|
|
|
||
| .. method:: DOMEventStream.reset() | ||
|
|
||
| .. method:: DOMEventStream.clear() |
There was a problem hiding this comment.
Oh, so it was not even documented?
| .. method:: DOMEventStream.clear() | |
| .. method:: clear() |
This is already in the "class:: DOMEventStream" block.
I think that it would be better to fix the documentation of clear() and reset() in separate PR before merging this PR (it can be easily backported).
There was a problem hiding this comment.
It looks like you referred to outdated code? Current implementation has method:: clear() without prefix.
Do you suggest submitting another PR just to fix the naming of DOMEventStream.reset()
| @@ -275,8 +282,11 @@ def _emit(self): | |||
|
|
|||
| def clear(self): | |||
| """clear(): Explicitly release parsing objects""" | |||
There was a problem hiding this comment.
"clear():" is redundant. The docstring still doesn't mention closing.
There was a problem hiding this comment.
The latest code has such wording:
"""Release the parsing objects.
The stream is closed if it was opened by parse().
"""
Do you think this is not appropriate?
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Also, is there a test for clear() with user stream?
Added |
|
I just fixed the |
When
pulldom.parse()is called with a filename string, it opens a file but the resultingDOMEventStreamnever closes it — noclose(), no context manager, andclear()just setsself.stream = Nonewithout closing.This adds resource management to
DOMEventStream:parse()opened the file via_owns_streamclose()that closes owned streams only__enter__/__exit__forwithstatement support__del__withResourceWarning(follows theiterparsepattern from xml.etree.ElementTree.iterparse() should emit ResourceWarning when not explicitly closed #140601)clear()to callclose()before releasing referencesThe
__del__binds_warn=warnings.warnas a default argument to handle interpreter shutdown safely, matching the pattern inElementTree.iterparse,subprocess.Popen, etc.User-provided streams (passed as file objects to
parse(), or viaparseString()) are never closed byDOMEventStream.📚 Documentation preview 📚: https://cpython-previews--148437.org.readthedocs.build/