enthought / pyface

pyface: traits-capable windowing framework
Other
105 stars 55 forks source link

TaskWindow still visible after shutdown #1107

Closed mdickinson closed 1 year ago

mdickinson commented 2 years ago

I'm seeing a situation in which a Pyface TaskWindow is still visible even after everything has been closed and shut down. At that point, there seems to be no evidence that the corresponding QWindow object still exists in either the Python domain or the C++ domain.

Here's a script to reproduce on my machine:

from pyface.qt import QtCore
from pyface.qt.QtGui import QApplication
from pyface.tasks.api import TaskWindow

def run_event_loop_for_fixed_time(time):
    app = QApplication.instance()

    timeout_timer = QtCore.QTimer()
    timeout_timer.setSingleShot(True)
    timeout_timer.setInterval(round(time * 1000.0))
    timeout_timer.timeout.connect(app.quit)

    timeout_timer.start()
    try:
        app.exec_()
    finally:
        timeout_timer.stop()

def report_window_destroyed(window):
    print("Window destroyed:", window)

def test_lifecycle():

    window = TaskWindow()
    window.open()
    run_event_loop_for_fixed_time(3.0)

    window.control.destroyed.connect(report_window_destroyed)
    window.close()
    run_event_loop_for_fixed_time(3.0)

def main():
    test_lifecycle()
    import gc; gc.collect()
    run_event_loop_for_fixed_time(3.0)

    # The task's QWindow is still visible at this point, but there's no
    # evidence of the application having any QObjects still alive.
    import pdb; pdb.set_trace()

if __name__ == "__main__":
    main()

When I run this script, at the point where the script drops into the pdb debugger, the window is still visible (but not fully functional: it can't be minimised or closed, for example), but looking at gc.get_objects(), I see no evidence of any Python-side QObjects still being alive, besides two QApplication objects.

Exciting screenshot of a blank window:

Screenshot 2022-02-16 at 14 49 59
mdickinson commented 2 years ago

Versions: Python 3.6.13 from EDM, Pyface 7.3.0-5, PyQt5 5.14.2-4, PyQt5-sip 12.7.2-1. I'll do some experimentation with other Qt wrappers.

mdickinson commented 2 years ago

This appears to be quite specific to the EDM runtime and Qt / PyQt versions.

I can reproduce with the following EDM combinations:

I haven't managed to reproduce at all with non-EDM setups. It looks as though this may be a Qt bug that's long since been fixed. As such, we probably shouldn't worry about this too much.

corranwebster commented 1 year ago

With the fixes made to TaskWindow closure in #1203 and changing the test method to connect to closure earlier:

def test_lifecycle():

    window = TaskWindow()
    window.open()
    print(window.control)
    window.control.destroyed.connect(report_window_destroyed)
    run_event_loop_for_fixed_time(3.0)

    window.close()
    window.destroy()
    run_event_loop_for_fixed_time(3.0)

(because QApplication.quit() closes windows, and so control gets set to None) this no longer has the window open past the close() - which becomes a no-op.

corranwebster commented 1 year ago

Quick update - it also works if you use app.exit (which doesn't close windows) instead of app.quit.

I think that that means that #1203 resolves this.