From d1006f3c21d7696edf3b1ac9a30e64079d9eb3c1 Mon Sep 17 00:00:00 2001 From: Reef Turner Date: Tue, 13 Apr 2021 11:42:10 +0800 Subject: [PATCH 1/4] Fix space in import statement --- source/_UIAHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py index 2b33b5d1c28..76e0047d0a3 100644 --- a/source/_UIAHandler.py +++ b/source/_UIAHandler.py @@ -37,7 +37,7 @@ import UIAUtils from comInterfaces import UIAutomationClient as UIA # F403: unable to detect undefined names -from comInterfaces .UIAutomationClient import * # noqa: F403 +from comInterfaces.UIAutomationClient import * # noqa: F403 import textInfos from typing import Dict from queue import Queue From 1e569bfced677ac8358b642b96d207729c153f46 Mon Sep 17 00:00:00 2001 From: Reef Turner Date: Tue, 13 Apr 2021 11:43:53 +0800 Subject: [PATCH 2/4] Less noisy destruction tests --- source/gui/__init__.py | 2 +- source/gui/settingsDialogs.py | 44 ++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 43b1c80b5d4..5b8674bf117 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -586,7 +586,7 @@ def terminate(): brailleViewer.destroyBrailleViewer() for instance, state in gui.SettingsDialog._instances.items(): - if state is gui.SettingsDialog._DIALOG_DESTROYED_STATE: + if state is gui.SettingsDialog.DialogState.DESTROYED: log.error( "Destroyed but not deleted instance of settings dialog exists: {!r}".format(instance) ) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 37c306f0ce7..3cad009820e 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -9,7 +9,9 @@ from abc import ABCMeta, abstractmethod import copy import os +from enum import IntEnum +import typing import wx from vision.providerBase import VisionEnhancementProviderSettings from wx.lib import scrolledpanel @@ -80,8 +82,10 @@ class SettingsDialog( class MultiInstanceError(RuntimeError): pass - _DIALOG_CREATED_STATE = 0 - _DIALOG_DESTROYED_STATE = 1 + class DialogState(IntEnum): + CREATED = 0 + DESTROYED = 1 + # holds instances of SettingsDialogs as keys, and state as the value _instances=weakref.WeakKeyDictionary() title = "" @@ -102,25 +106,39 @@ def __new__(cls, *args, **kwargs): "Creating new settings dialog (multiInstanceAllowed:{}). " "State of _instances {!r}".format(multiInstanceAllowed, instancesState) ) - if state is cls._DIALOG_CREATED_STATE and not multiInstanceAllowed: + if state is cls.DialogState.CREATED and not multiInstanceAllowed: raise SettingsDialog.MultiInstanceError("Only one instance of SettingsDialog can exist at a time") - if state is cls._DIALOG_DESTROYED_STATE and not multiInstanceAllowed: + if state is cls.DialogState.DESTROYED and not multiInstanceAllowed: # the dialog has been destroyed by wx, but the instance is still available. This indicates there is something # keeping it alive. log.error("Opening new settings dialog while instance still exists: {!r}".format(firstMatchingInstance)) obj = super(SettingsDialog, cls).__new__(cls, *args, **kwargs) - SettingsDialog._instances[obj] = cls._DIALOG_CREATED_STATE + SettingsDialog._instances[obj] = cls.DialogState.CREATED return obj def _setInstanceDestroyedState(self): - if log.isEnabledFor(log.DEBUG): - instancesState = dict(SettingsDialog._instances) - log.debug( - "Setting state to destroyed for instance: {!r}\n" - "Current _instances {!r}".format(self, instancesState) - ) - if self in SettingsDialog._instances: - SettingsDialog._instances[self] = self._DIALOG_DESTROYED_STATE + # prevent race condition with object deletion + # prevent deletion of the object while we work on it. + nonWeak: typing.Dict[SettingsDialog, SettingsDialog.DialogState] = dict(SettingsDialog._instances) + + if ( + self in SettingsDialog._instances + # Because destroy handlers are use evt.skip, _setInstanceDestroyedState may be called many times + # prevent noisy logging. + and self.DialogState.DESTROYED != SettingsDialog._instances[self] + ): + if log.isEnabledFor(log.DEBUG): + instanceStatesGen = ( + f"{instance.title} - {state.name}" + for instance, state in nonWeak.items() + ) + instancesList = list(instanceStatesGen) + log.debug( + f"Setting state to destroyed for instance: {self.title} - {self.__class__.__qualname__} - {self}\n" + f"Current _instances {instancesList}" + ) + SettingsDialog._instances[self] = self.DialogState.DESTROYED + def __init__( self, parent, From c7e32dacfa84dcc9a3d6a06cc5a9114daa8b5c3d Mon Sep 17 00:00:00 2001 From: Reef Turner Date: Wed, 14 Apr 2021 18:49:20 +0800 Subject: [PATCH 3/4] More detail in Destroyed but not deleted instance log message. --- source/gui/__init__.py | 9 +++++++-- source/gui/settingsDialogs.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/source/gui/__init__.py b/source/gui/__init__.py index 5b8674bf117..d2fdc0f02d9 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -585,10 +585,15 @@ def terminate(): import brailleViewer brailleViewer.destroyBrailleViewer() - for instance, state in gui.SettingsDialog._instances.items(): + # prevent race condition with object deletion + # prevent deletion of the object while we work on it. + nonWeak: typing.Dict[SettingsDialog, SettingsDialog.DialogState] = dict(gui.SettingsDialog._instances) + + for instance, state in nonWeak.items(): if state is gui.SettingsDialog.DialogState.DESTROYED: log.error( - "Destroyed but not deleted instance of settings dialog exists: {!r}".format(instance) + "Destroyed but not deleted instance of gui.SettingsDialog exists" + f": {instance.title} - {instance.__class__.__qualname__} - {instance}" ) else: log.debug("Exiting NVDA with an open settings dialog: {!r}".format(instance)) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 3cad009820e..7201aa2a323 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -87,7 +87,7 @@ class DialogState(IntEnum): DESTROYED = 1 # holds instances of SettingsDialogs as keys, and state as the value - _instances=weakref.WeakKeyDictionary() + _instances = weakref.WeakKeyDictionary() title = "" helpId = "NVDASettings" shouldSuspendConfigProfileTriggers = True From 56c3f43af14cf86585fa8a30f961cfad9dc32335 Mon Sep 17 00:00:00 2001 From: Reef Turner Date: Wed, 14 Apr 2021 19:10:03 +0800 Subject: [PATCH 4/4] Fix lint errors --- source/gui/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/gui/__init__.py b/source/gui/__init__.py index d2fdc0f02d9..b812bab9324 100644 --- a/source/gui/__init__.py +++ b/source/gui/__init__.py @@ -5,6 +5,7 @@ # This file is covered by the GNU General Public License. # See the file COPYING for more details. +import typing import time import os import sys @@ -24,6 +25,7 @@ import queueHandler import core from . import guiHelper +from . import settingsDialogs from .settingsDialogs import * from .inputGestures import InputGesturesDialog import speechDictHandler @@ -587,10 +589,11 @@ def terminate(): # prevent race condition with object deletion # prevent deletion of the object while we work on it. - nonWeak: typing.Dict[SettingsDialog, SettingsDialog.DialogState] = dict(gui.SettingsDialog._instances) + _SettingsDialog = settingsDialogs.SettingsDialog + nonWeak: typing.Dict[_SettingsDialog, _SettingsDialog] = dict(_SettingsDialog._instances) for instance, state in nonWeak.items(): - if state is gui.SettingsDialog.DialogState.DESTROYED: + if state is _SettingsDialog.DialogState.DESTROYED: log.error( "Destroyed but not deleted instance of gui.SettingsDialog exists" f": {instance.title} - {instance.__class__.__qualname__} - {instance}"