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

Disconnect qt signals in qt implementation of pyface.tasks #542

Merged
merged 6 commits into from
Jun 24, 2020

Conversation

ievacerny
Copy link
Contributor

Part of #258

Distinct things this PR does:

  1. DockPane - disconnects signals from its control. Low risk - low reward kind of change. Doesn't hurt to add this.

  2. Deletes connections to shortcuts via _connections_to_remove in classes providing IEditorAreaPane. It is good to disconnect these shortcuts because if something goes wrong and the objects are not deleted, the handlers can still be activated via keyboard events. So the reward is fairly high. The risk with current code is low because it is only disconnecting what is added to the list and there are no other disconnects. The problems might arise if someone decides to disconnect the shortcuts elsewhere and doesn't remove them from the list. But maybe that should be considered a case of "Don't do that".

  3. Removes reference to active_tabwidget from SplitEditorAreaPane. This was preventing the widget from being deleted together with the main control. Low risk - high reward kind of change. Although not directly related to signals (and thus can be separated from this).

  4. EditorAreaPane - disconnects signals from its control. The handlers do work with UI elements accessible via control but then the signals are emitted from the same control. If control is set to None between the signal was emitted and slot was executed, there could be problems. So I would say that this is a low risk - low medium reward kind of change.

  5. Removes reference to active_editor from AdvancedEditorAreaPane. The editor here is a python object so holding a reference to it doesn't really do anything (its control is destroyed separately). But other classes implementing IEditorAreaPane are removing this reference via remove_editor method, so I added that line of code here as well. No risk - no reward kind of change. Done for consistency.

  6. AdvancedEditorAreaPane - tells its control to disconnect what it had connected. In this case it had connected a slot to focusChanged signal of the main application, so this is important to disconnect. Low risk - high reward kind of change.

  7. When EditorAreaWidget destroys and editor_widget, it also tells the widget to disconnect what it had connected. The signals and slots belong to the same object but in the slot the objects tries to access an attribute on the parent, which might cause problems. Low risk - medium reward.

  8. After disconnecting its own signals, EditorWidget tells its titlebarWidget (EditorTitleBarWidget) to disconnect what it had connected. The issue here is that EditorTitleBarWidget doesn't keep a reference to the slot it connected its signal to. The slot can be accessed from EditorWidget so it is easy to pass that in. However, an assumption has been made here that the editor of a certain editor_widget doesn't change. Making assumptions here is quite risky and this disconnect doesn't cover the case where new EditorTitleBarWidget is set as the TitleBarWidget and the old one is discarded. I'm not sure how qt handles disposal in that case but I assume that it destroys the old object and by doing so destroys the connections as well. So given that this added disconnect covers only a small case that is probably handled as expected by qt, this is a high risk - low benefit kind of change.

Connections that this PR doesn't remove:

  1. In childEvent method of EditorAreaWidget (control of Advanced EditorAreaPane) a couple of slots are connected to a couple of signals of the child. I couldn't find an easy way to disconnect these signals, so I didn't add these changes. But given that this is a child signal connected to a parent slot and both are qt objects, qt should handle this situation well on its own.

  2. DraggableTabWidget that are children of SplitEditorAreaPane control make a couple of connections in their __init__. The control can have a tree hierarchy structure with DraggableTabWidgets so there's no easy way to remove these connections. But given that both signals and slots belong to the same object, they shouldn't cause any problems.

  3. In ceate_empty_widget of DraggableTabWidget buttons are created and their signals are connected to callbacks provided in top SplitEditorAreaPane. Again, there's no easy way to remove these connections. I think these connection can be problematic and would say that removing them would be a medium reward. But I think the code needed to disconnect them would be much riskier. I would like to hear more opinions on this.

@ievacerny ievacerny self-assigned this Jun 12, 2020
@codecov-commenter
Copy link

codecov-commenter commented Jun 12, 2020

Codecov Report

Merging #542 into master will increase coverage by 0.85%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #542      +/-   ##
==========================================
+ Coverage   36.99%   37.85%   +0.85%     
==========================================
  Files         470      470              
  Lines       26027    26134     +107     
  Branches     3961     3971      +10     
==========================================
+ Hits         9629     9892     +263     
+ Misses      15977    15809     -168     
- Partials      421      433      +12     
Impacted Files Coverage Δ
pyface/ui/qt4/tasks/advanced_editor_area_pane.py 51.72% <100.00%> (+33.29%) ⬆️
pyface/ui/qt4/tasks/dock_pane.py 69.04% <100.00%> (+2.81%) ⬆️
pyface/ui/qt4/tasks/editor_area_pane.py 55.88% <100.00%> (+33.66%) ⬆️
pyface/ui/qt4/tasks/split_editor_area_pane.py 65.41% <100.00%> (+1.02%) ⬆️
pyface/ui/qt4/code_editor/code_widget.py 41.75% <0.00%> (-0.44%) ⬇️
pyface/ui/qt4/console/console_widget.py 27.89% <0.00%> (-0.43%) ⬇️
pyface/ui/qt4/util/image_helpers.py 97.14% <0.00%> (+0.08%) ⬆️
pyface/ui/qt4/dialog.py 94.66% <0.00%> (+0.72%) ⬆️
pyface/tasks/i_editor_area_pane.py 68.75% <0.00%> (+1.56%) ⬆️
... and 8 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 08ba6c2...c68aed8. Read the comment docs.

Copy link
Contributor

@kitchoi kitchoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code and your assessment both LGTM.

pyface/ui/qt4/tasks/editor_area_pane.py Outdated Show resolved Hide resolved
pyface/ui/qt4/tasks/advanced_editor_area_pane.py Outdated Show resolved Hide resolved
Copy link
Contributor

@kitchoi kitchoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Mostly LGTM. Basically there is only one comment about the if self.control is not None guard.

pyface/ui/qt4/tasks/advanced_editor_area_pane.py Outdated Show resolved Hide resolved
pyface/ui/qt4/tasks/editor_area_pane.py Outdated Show resolved Hide resolved
pyface/ui/qt4/tasks/split_editor_area_pane.py Outdated Show resolved Hide resolved
@kitchoi
Copy link
Contributor

kitchoi commented Jun 19, 2020

Looks like we have disturbed the balance of the forces again... :(

There are a number of error traceback following some of the tests that have nothing to do with any of the widgets, e.g.:

def test_assert_eventually_true_in_gui_already_true(self):
my_list = ["bob"]
self.assertEventuallyTrueInGui(lambda: len(my_list) > 0)

test_assert_eventually_true_in_gui_already_true (pyface.ui.qt4.util.tests.test_gui_test_assistant.TestGuiTestAssistant) ... Traceback (most recent call last):
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/ui/qt4/gui.py", line 176, in event
    self._callable(*self._args, **self._kw)
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/i_window.py", line 233, in close
    self.destroy()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/tasks/task_window.py", line 95, in destroy
    self._window_backend.destroy()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/ui/qt4/tasks/task_window_backend.py", line 56, in destroy
    app.focusChanged.disconnect(self._focus_changed_signal)
TypeError: 'method' object is not connected
ok

Those tests did not fail, though. :/

And there are a couple that actually register as test failure and cause the CI build to fail:

======================================================================
ERROR: test_react_to_dock_pane_removed (pyface.tasks.tests.test_dock_pane_toggle_group.DockPaneToggleGroupTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/tasks/tests/test_dock_pane_toggle_group.py", line 92, in tearDown
    self.window.destroy()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/tasks/task_window.py", line 101, in destroy
    self._destroy_state(state)
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/tasks/task_window.py", line 364, in _destroy_state
    state.menu_bar_manager.destroy()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/action/action_manager.py", line 178, in destroy
    group.destroy()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/action/group.py", line 130, in destroy
    item.destroy()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/ui/qt4/action/menu_manager.py", line 76, in destroy
    menu.dispose()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/ui/qt4/action/menu_manager.py", line 168, in dispose
    self.clear()
  File "/home/travis/.edm/envs/pyface-test-3.6-pyqt/lib/python3.6/site-packages/pyface-7.1.0.dev17-py3.6.egg/pyface/ui/qt4/action/menu_manager.py", line 182, in clear
    super(_Menu, self).clear()
RuntimeError: wrapped C/C++ object of type _Menu has been deleted

@ievacerny
Copy link
Contributor Author

I think both of the tracebacks are caused by MenuManager and ToolBarManager sharing the same list of menus and toolbars (#541 (comment)). This should be fixed by #548, so this PR should wait until #548 is merged.

@ievacerny
Copy link
Contributor Author

Closing and reopening to restart CI jobs

@ievacerny ievacerny closed this Jun 23, 2020
@ievacerny ievacerny reopened this Jun 23, 2020
Copy link
Contributor

@kitchoi kitchoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. LGTM!

@ievacerny ievacerny merged commit 351ec34 into master Jun 24, 2020
@ievacerny ievacerny deleted the maint/258-audit-qt-cleanup5 branch June 24, 2020 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants