Skip to content
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

Event loop close method doesn't fully clean up after itself #73

Open
mdickinson opened this issue Jan 16, 2023 · 3 comments
Open

Event loop close method doesn't fully clean up after itself #73

mdickinson opened this issue Jan 16, 2023 · 3 comments
Assignees
Labels
bug Something isn't working

Comments

@mdickinson
Copy link

mdickinson commented Jan 16, 2023

We're seeing an issue where the close method of a QSelectorEventLoop doesn't completely clean up resources associated to that event loop.

Reproducer

The following code should reproduce:

import gc
import PySide6.QtGui
import qasync

async def app_main():
    # Dummy application.
    pass

def run_app(qt_app):
    event_loop = qasync.QEventLoop(qt_app)
    event_loop.run_until_complete(app_main())
    event_loop.close()

qt_app = PySide6.QtGui.QGuiApplication()
for _ in range(10):
    run_app(qt_app)
    gc.collect()
    loops = [
        obj for obj in gc.get_objects()
        if type(obj).__name__ == "QSelectorEventLoop"
    ]
    print("number of event loop objects: ", len(loops))
    del loops

When I run the above on my machine, I get:

(qasync) mdickinson@mirzakhani Desktop % python ~/Desktop/qasync_leak.py
number of event loop objects:  1
number of event loop objects:  2
number of event loop objects:  3
number of event loop objects:  4
number of event loop objects:  5
number of event loop objects:  6
number of event loop objects:  7
number of event loop objects:  8
number of event loop objects:  9
number of event loop objects:  10

The expectation / hope was that the number of event loops would be stable.

Diagnosis

Here's a graph showing all the ancestors of one of the leaked QSelectorEventLoop objects.
refcycle

Those two lambda objects at the top have no referrers (in the sense of gc.get_referrers); they're apparently being kept alive by PySide6. Those lambdas correspond to these two lines:

I believe that if we add the corresponding signal disconnects at event loop close time, then this will fix the above issue.

System details

  • Python 3.10 venv
  • qasync 0.23.0 installed from PyPI (via pip)
  • PySide6 6.4.2 installed from PyPI (via pip)
  • macOS 12.6.2 on an Intel MacBook Pro

Context

In case you're wondering why anyone would be creating QEventLoops repeatedly, the answer is unit testing. We're using qasync to provide an asyncio event loop for an embedded IPython kernel to use within a Qt-based application. In our test suite we have many tests that create and then tear down that asyncio event loop, and we were observing test interactions as a result of not being able to cleanly clean up all the resources associated to the event loop. (The Qt application itself is a singleton, of course, so we don't try to clean that up between tests.)

@MatthieuDartiailh
Copy link

On a similar note QEventLoop does not clean up asyncio.events._set_running_loop which can cause issue when multiple different event loops are used over time (I have a testing situation where this arises).

@hosaka
Copy link
Collaborator

hosaka commented Apr 11, 2023

Hi @mdickinson, thank you for a detailed repro and analysis. I will try to address this issue in the coming weeks.
@MatthieuDartiailh this has been fixed in #71 and present in 0.24.0

@hosaka hosaka added the bug Something isn't working label Apr 11, 2023
@hosaka hosaka self-assigned this Apr 11, 2023
@aboys-cb
Copy link

Hello, after switching this example from pyside6 to pyqt6, this problem disappears. I don't know if it's a bug in qasync or pyside6. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants