Conversation
|
It would be good if we had a handle on when it was necessary to manually call destroy on children in wx. Have you seen this in the docs? |
|
So the Window deletion overiview says:
|
It isn't documented anywhere officially as far as I can tell, just forums/blogs like the following:
The original commit which introduced this had a good message 8ab9d48:
The cases mentioned surrounding Dialogs should be fixed with #12183 |
|
Right, thanks for looking into this. It seems the key is:
Would it make sense to ensure that they are children? |
|
This is also all the same for our taskbar menu |
|
Ah, good find. In this case, perhaps we can add these to a list of "must be explicitly destroyed on exit" controls on the App of somewhere appropriate and have it iterated over calling destroy? I'm thinking that if we call this out on the app that there are certain types of controls that need special handling, how to differentiate them from the others, and a mechanism for destroying them on shutdown it will be easier to understand in the future. |
|
I think the reason why they were being destroyed from where they are currently is because the same class is initialising them. I could move it all to |
See test results for failed build of commit bf23c9e0a2 |
See test results for failed build of commit bf23c9e0a2 |
See test results for failed build of commit 7c462fa831 |
See test results for failed build of commit c44c28a0d6 |
feerrenrut
left a comment
There was a problem hiding this comment.
Could you also add a note into the "safe exit" function you added previously to point out that wx objects that don't inherit from wx.Window (eg sysTrayIcon, Menu) need to be manually destroyed. That function is a logical place to look for an explanation if NVDA has problems during exit in the future.
| def _onDestroy(self, evt): | ||
| evt.Skip() # Allow other handlers to process this event. |
There was a problem hiding this comment.
Why should evt.Skip() be removed? There shouldn't be any other parent controls, but if there were?
There was a problem hiding this comment.
I've added a comment that makes the reason for this more explicit
See test results for failed build of commit 1687ecb41b |
See test results for failed build of commit 1687ecb41b |
See test results for failed build of commit cbf3bc3037 |
| # wx destroys child Windows automatically but `wx.adv.TaskBarIcon` is not a window | ||
| # so it must be set to be destroyed when destroying our main frame window (#12243) | ||
| log.debug(f"destroying systray icon") | ||
| wx.CallAfter(self.sysTrayIcon.Destroy) |
There was a problem hiding this comment.
Why is callAfter necessary here?
There was a problem hiding this comment.
I can't find this in any official documentation, but when doing the original safeAppExit PR I found several recommendations in forums to use this method as it queues destruction events safer. Calling Destroy directly here causes a crash on the installation process.
| self.Hide() | ||
|
|
||
| def _onDestroy(self, evt: wx.EVT_WINDOW_DESTROY): | ||
| self.Skip(skip=False) # we should block the destruction events until the sysTrayIcon is destroyed |
There was a problem hiding this comment.
"until" makes it sound like this logic will change at some point.
Hopefully I'm not alone in finding skip hard to memorize, I wish they had named it in the affirmative eg handled.
The docs say:
This method can be used inside an event handler to control whether further event handlers bound to this event will be called after the current one returns.
Without Skip (or equivalently if Skip(false) is used), the event will not be processed any more. If Skip(true) is called, the event processing system continues searching for a further handler function for this event, even though it has been processed already in the current handler.
In general, it is recommended to skip all non-command events to allow the default handling to take place. The command events are, however, normally not skipped as usually a single command such as a button click or menu item selection must only be processed by one handler.
This sounds like it will do the opposite of what the comment here says.
I've just noticed that this calls self.Skip rather than evt.Skip I assume this is a mistake?
There was a problem hiding this comment.
I did find this hard to parse. I'm not sure what the default handler does for a wx.EVT_WINDOW_DESTROY event, but this is a command event so this behaviour is recommended and crashes otherwise. This event fires just before self.Destroy - which is what the comment was trying to allude to in a misleading way. I think the comment where the event handler is being bound should makes this clear anyway.
And yes, the self/evt typo was a mistake.
See test results for failed build of commit a4fd79176c |
See test results for failed build of commit 324f5a1724 |
| evt.Skip(skip=False) # blocks other handlers as wxCommandEvents must only be processed by one handler | ||
| # wx destroys child Windows automatically but `wx.adv.TaskBarIcon` is not a window | ||
| # so it must be set to be destroyed when destroying our main frame window (#12243) | ||
| log.debug(f"destroying systray icon") |
There was a problem hiding this comment.
Why you're using f-string here?
| wx.CallAfter(window.EndModal, wx.ID_CLOSE_ALL) | ||
| if isinstance(window, MainFrame): | ||
| log.info(f"destroying main frame during exit process") | ||
| log.debug(f"destroying main frame during exit process") |
There was a problem hiding this comment.
Same here w.r.t f-string being unnecessary.
| def Destroy(self): | ||
| # wx.Windows destroy child Windows automatically but wx.Menu and TaskBarIcon don't inherit from | ||
| # wx.Window. The menu must be manually destroyed when destroying our system tray icon (#12243) | ||
| log.debug(f"destroying systray menu during exit process") |
See test results for failed build of commit c127e088d9 |
|
closing as it appears this can't be fixed without inducing a crash |
Link to issue number:
Closes #12238
Summary of the issue:
System tray icons are not cleaned properly when exiting/restarting NVDA. This is due to an incorrect assumption in #12183 that the manual destruction of the system tray icon was no longer necessary.
Description of how this pull request fixes the issue:
Revert the changes in #12183 that manually destroyed the system tray icon and add logging to the process.
Garbage collection is not enough with wx; Destroy() must eventually be called on all objects. wx does handle destruction of children, but the system tray icon and its menu are not children, so we need to handle them explicitly. 8ab9d48
Testing strategy:
Manually test the steps in #12238
Ensure the silent installation process doesn't crash (ie a regression on what #12183 fixed for #12153)
Known issues with pull request:
None
Change log entry:
None, fixes regression
Code Review Checklist: