-
-
Notifications
You must be signed in to change notification settings - Fork 826
Fix Enum Cyclic Ref #12617
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
Merged
Merged
Fix Enum Cyclic Ref #12617
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
1e575e3
fix enum cyclic ref on value error
feerrenrut 91e534b
devDocs for debugging unreachableObjects
feerrenrut 6f84789
fix lint
feerrenrut 87ea3a7
Update devDocs/unreachableObjects.md
feerrenrut 573fc34
Update devDocs/unreachableObjects.md
feerrenrut 372dd0f
add wx MonkeyPatches to package
feerrenrut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # Garbage collection errors | ||
| NVDA's `garbageHandler.py` monitors Python's cyclic garbage collector and reports | ||
| on objects that are unreachable. | ||
| Cyclic references are typically a symptom of bad design, and can cause major problems for certain objects. | ||
| For instance, cyclic references involving COM objects may cause a deadlock if the garbage collector happens to break the cycle and release the COM object in the wrong thread. | ||
|
|
||
| ## How to know about a cyclic reference? | ||
| The log may contain errors like the following. | ||
| ``` | ||
| WARNING - garbageHandler.notifyObjectDeletion (10:45:23.171) - MainThread (21820): | ||
| Garbage collector has found one or more unreachable objects. See further warnings for specific objects. | ||
| ... | ||
| WARNING - garbageHandler.notifyObjectDeletion (10:45:23.171) - MainThread (21820): | ||
| Deleting unreachable object <eventHandler._EventExecuter object at 0x1AC15350> | ||
| ERROR - garbageHandler._collectionCallback (10:45:23.172) - MainThread (21820): | ||
| Found at least 1 unreachable objects in run | ||
| ``` | ||
|
|
||
| ## How to debug a cyclic reference? | ||
|
|
||
| Once you can reliably reproduce the log error, you can tell the garbage collector to save all unreachable objects. | ||
| After an unreachable object is detected the references to the unreachable object can be inspected via the python console. | ||
| Inspecting this should give you a fair idea of where the issue is occurring. | ||
|
|
||
| 1. Open the NVDA Python console `NVDA+control+z` | ||
| 1. Enable saving all objects: | ||
| ``` python | ||
| import gc | ||
| gc.set_debug(gc.DEBUG_SAVEALL) | ||
| ``` | ||
| 1. Reproduce the unreachable object error. | ||
| 1. If garbage collection errors have not yet been logged, force a collect by calling: | ||
| ``` python | ||
| gc.collect() | ||
| ``` | ||
|
|
||
| 1. All unreachable objects will now be stored in `gc.garbage`. | ||
| It may be a very large list. | ||
| Some tricks for narrowing this list down: | ||
| - From the log, you can get the memory address (`id`) of the object. | ||
| Then use: | ||
| ``` python | ||
| memoryAddress = 0xabcd123 | ||
| obj = None | ||
| for o in gc.garbage: | ||
| if memoryAddress == id(o) | ||
| obj = o | ||
| ``` | ||
| - Listing the types collected, look for the type(s) matching the log message: | ||
| ``` python | ||
| for index, o in enumerate(gc.garbage): | ||
| print(index, type(o)) | ||
| ``` | ||
| 1. Once you have a reference (`obj`) to the unreachable object, see what other objects refer to an object you can call. | ||
| You can do this by using `gc.get_referrers`. | ||
| The python console has a reference to `obj`, there may be a lot of output. | ||
| You can reduce this by looking at the types and following the most relevant. | ||
| ``` python | ||
| for index, o in enumerate(gc.get_referrers(obj)): | ||
| print(index, type(o)) | ||
| ``` | ||
| 1. Continue following the references to build a picture of the cycle. | ||
|
|
||
| ## Typical problems | ||
| Some examples of common issues: | ||
| - Exceptions caught and assigned to a local variable. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # A part of NonVisual Desktop Access (NVDA) | ||
| # Copyright (C) 2021 NV Access Limited | ||
| # This file is covered by the GNU General Public License. | ||
| # See the file COPYING for more details. | ||
|
|
||
| from . import wxMonkeyPatches | ||
|
|
||
|
|
||
| applyWxMonkeyPatches = wxMonkeyPatches.apply | ||
|
|
||
|
|
||
| def applyMonkeyPatches(): | ||
| # Apply several monkey patches to comtypes | ||
| # F401 - imported but unused: Patches are applied during import | ||
| from . import comtypesMonkeyPatches # noqa: F401 | ||
|
|
||
| # Apply patches to Enum, prevent cyclic references on ValueError during construction | ||
| from . import enumPatches | ||
| enumPatches.replace__new__() | ||
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # A part of NonVisual Desktop Access (NVDA) | ||
| # Copyright (C) 2021 NV Access Limited | ||
| # This file is covered by the GNU General Public License. | ||
| # See the file COPYING for more details. | ||
|
|
||
|
|
||
| def replace__new__(): | ||
| import enum | ||
| # prevent cyclic references on ValueError during construction | ||
|
feerrenrut marked this conversation as resolved.
|
||
| enum.Enum.__new__ = _replacement__new__ | ||
|
|
||
|
|
||
| def _replacement__new__(cls, value): | ||
| """ Copied from python standard library enum.py class Enum. | ||
| Prevent cyclic references on ValueError during construction. | ||
| Local variable exc must be deleted, otherwise: | ||
| - ref to exc held by the frame | ||
| - ref to traceback held by exc | ||
| - ref to frame held by traceback | ||
| """ | ||
| # all enum instances are actually created during class construction | ||
| # without calling this method; this method is called by the metaclass' | ||
| # __call__ (i.e. Color(3) ), and by pickle | ||
| if type(value) is cls: | ||
| # For lookups like Color(Color.RED) | ||
| return value | ||
| # by-value search for a matching enum member | ||
| # see if it's in the reverse mapping (for hashable values) | ||
| try: | ||
| return cls._value2member_map_[value] | ||
| except KeyError: | ||
| # Not found, no need to do long O(n) search | ||
| pass | ||
| except TypeError: | ||
| # not there, now do long search -- O(n) behavior | ||
| for member in cls._member_map_.values(): | ||
| if member._value_ == value: | ||
| return member | ||
| # still not found -- try _missing_ hook | ||
| try: | ||
| result = cls._missing_(value) | ||
| except Exception as e: | ||
| e.__context__ = ValueError("%r is not a valid %s" % (value, cls.__name__)) | ||
| raise e | ||
|
|
||
| if isinstance(result, cls): | ||
| return result | ||
|
|
||
| with ValueError( | ||
| "%r is not a valid %s" % (value, cls.__name__) | ||
| ) as ve_exc: | ||
| if result is None: | ||
| raise ve_exc | ||
|
|
||
| te_exc = TypeError( | ||
| 'error in %s._missing_: returned %r instead of None or a valid member' | ||
| % (cls.__name__, result) | ||
| ) | ||
| te_exc.__context__ = ve_exc | ||
| raise te_exc | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # A part of NonVisual Desktop Access (NVDA) | ||
| # Copyright (C) 2021 NV Access Limited | ||
| # This file is covered by the GNU General Public License. | ||
| # See the file COPYING for more details. | ||
|
|
||
|
|
||
| def apply(mainFrame, winUser, wx): | ||
| # In wxPython >= 4.1, | ||
| # wx.CallAfter no longer executes callbacks while NVDA's main thread is within a popup menu or message box. | ||
| # To work around this, | ||
| # MonkeyPatch wx.CallAfter to | ||
| # post a WM_NULL message to our top-level window after calling the original CallAfter, | ||
| # which causes wx's event loop to wake up enough to execute the callback. | ||
| old_wx_CallAfter = wx.CallAfter | ||
|
|
||
| def wx_CallAfter_wrapper(func, *args, **kwargs): | ||
| old_wx_CallAfter(func, *args, **kwargs) | ||
| # mainFrame may be None as NVDA could be terminating. | ||
| topHandle = mainFrame.Handle if mainFrame else None | ||
| if topHandle: | ||
| winUser.PostMessage(topHandle, winUser.WM_NULL, 0, 0) | ||
|
|
||
| wx.CallAfter = wx_CallAfter_wrapper |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.