Author vstinner
Recipients vstinner
Date 2019-06-13.01:43:10
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1560390190.75.0.577750929769.issue37261@roundup.psfhosted.org>
In-reply-to
Content
test_io leaks references on AMD64 Fedora Rawhide Refleaks 3.8:
https://buildbot.python.org/all/#/builders/229/builds/10

test_io leaked [23208, 23204, 23208] references, sum=69620
test_io leaked [7657, 7655, 7657] memory blocks, sum=22969


The issue has been introduced by my change:

commit c15a682603a47f5aef5025f6a2e3babb699273d6
Author: Victor Stinner <vstinner@redhat.com>
Date:   Thu Jun 13 00:23:49 2019 +0200

    bpo-37223: test_io: silence destructor errors (GH-14031)
    
    * bpo-18748: Fix _pyio.IOBase destructor (closed case) (GH-13952)
    
    _pyio.IOBase destructor now does nothing if getting the closed
    attribute fails to better mimick _io.IOBase finalizer.
    
    (cherry picked from commit 4f6f7c5a611905fb6b81671547f268c226bc646a)
    
    * bpo-37223: test_io: silence destructor errors (GH-13954)
    
    Implement also MockNonBlockWriterIO.seek() method.
    
    (cherry picked from commit b589cef9c4dada2fb84ce0fae5040ecf16d9d5ef)
    
    * bpo-37223, test_io: silence last 'Exception ignored in:' (GH-14029)
    
    Use catch_unraisable_exception() to ignore 'Exception ignored in:'
    error when the internal BufferedWriter of the BufferedRWPair is
    destroyed. The C implementation doesn't give access to the
    internal BufferedWriter, so just ignore the warning instead.
    
    (cherry picked from commit 913fa1c8245d1cde6edb4254f4fb965cc91786ef)


It seems like the root issue is the usage of catch_unraisable_exception() in test_io.

class catch_unraisable_exception:
    def __init__(self):
        self.unraisable = None
        self._old_hook = None

    def _hook(self, unraisable):
        self.unraisable = unraisable

    def __enter__(self):
        self._old_hook = sys.unraisablehook
        sys.unraisablehook = self._hook
        return self

    def __exit__(self, *exc_info):
        # Clear the unraisable exception to explicitly break a reference cycle
        del self.unraisable
        sys.unraisablehook = self._old_hook

*Sometimes* "del self.unraisable" of __exit__() triggers a new unraisable exception which calls again the _hook() method which sets again the unraisable attribute.

catch_unraisable_exception resurects _io.BufferedWriter objects, and then "del self.unraisable" calls again their finalizer: iobase_finalize() is called again, and iobase_finalize() calls PyErr_WriteUnraisable() on close() failure.
History
Date User Action Args
2019-06-13 01:43:10vstinnersetrecipients: + vstinner
2019-06-13 01:43:10vstinnersetmessageid: <1560390190.75.0.577750929769.issue37261@roundup.psfhosted.org>
2019-06-13 01:43:10vstinnerlinkissue37261 messages
2019-06-13 01:43:10vstinnercreate