• Kirill Smelkov's avatar
    golang: pyselect: Fix tx object reference leak on error exit · e9180de1
    Kirill Smelkov authored
    Before passing objects to _chanselect for send, pyselect increfs them, as just
    send does, to indicate that one object reference is passed to channel buffer.
    On exit, since only one case is actually executed by select, pyselect needs to
    decref incref'ed object from not executed cases.
    
    Pyselect already implements the latter cleanup, but currently the cleanup is
    executed only if control flow reaches _chanselect at all. Which is a bug, since
    pyselect can panic or raise an exception just in the middle of preparation phase.
    
    -> Fix it by associating the finally-decref cleanup with whole
    prepare+_chanselect code.
    
    Without the fix, the second part of added test (abnormal exit) fails e.g. like:
    
            @mark.skipif(not hasattr(sys, 'getrefcount'),   # skipped e.g. on PyPy
                         reason="needs sys.getrefcount")
            def test_select_refleak():
                ch1 = chan()
                ch2 = chan()
                obj1 = object()
                obj2 = object()
                tx1 = (ch1.send, obj1)
                tx2 = (ch2.send, obj2)
    
                # normal exit
                gc.collect()
                nref1 = sys.getrefcount(obj1)
                nref2 = sys.getrefcount(obj2)
                _, _rx = select(
                    tx1,        # 0
                    tx2,        # 1
                    default,    # 2
                )
                assert (_, _rx) == (2, None)
                gc.collect()
                assert sys.getrefcount(obj1) == nref1
                gc.collect()
                assert sys.getrefcount(obj1) == nref2
    
                # abnormal exit
                with raises(AttributeError) as exc:
                    select(
                        tx1,        # 0
                        tx2,        # 1
                        'zzz',      # 2 causes pyselect to panic
                    )
                assert exc.value.args == ("'str' object has no attribute '__self__'",)
                gc.collect()
        >       assert sys.getrefcount(obj1) == nref1
        E       assert 4 == 3
        E         -4
        E         +3
    
        golang/golang_test.py:690: AssertionError
    
    The bug was introduced in 3b241983 (Port/move channels to C/C++/Pyx).
    e9180de1
golang_test.py 27.1 KB