Repository: nvaccess/nvda

Issue:
NVDA may sometimes freeze within Queue.put
### Steps to reproduce:
#### Case 1
1. Configure NVDA to use a braille display, or open NVDA's Braille Viewer from the Tools menu.
2. Open a new document in Microsoft Word.
3. Keep typing random text rather fast with lots of spaces such that the words are all spelling errors.
### Actual behavior:
NVDA eventually freezes.

### Expected behavior:
NVDA should not freeze.

### technical info
It seems that the Python garbage collector is allowed to run within Queue.put.
This then means that comtypes might call Release on a COM object in a __del__ method, which in tern may cause a dead lock between NvDA and NVDAhelper in-process code in an app like Microsoft Word, when handling typed characters.

See this Python stack:
```
Python stack for thread 4108:
  File "C:\Users\mick\programming\git\nvda\source\NVDAHelper.py", line 401, in nvdaControllerInternal_typedCharacterNotify
    eventHandler.queueEvent("typedCharacter",focus,ch=ch)
  File "C:\Users\mick\programming\git\nvda\source\eventHandler.py", line 44, in queueEvent
    queueHandler.queueFunction(queueHandler.eventQueue,_queueEventCallback,eventName,obj,kwargs)
  File "C:\Users\mick\programming\git\nvda\source\queueHandler.py", line 37, in queueFunction
    queue.put_nowait((func,args,kwargs))
  File "C:\Program Files (x86)\Python37-32\lib\queue.py", line 190, in put_nowait
    return self.put(item, block=False)
  File "C:\Program Files (x86)\Python37-32\lib\queue.py", line 151, in put
    self.not_empty.notify()
  File "C:\Program Files (x86)\Python37-32\lib\threading.py", line 348, in notify
    waiters_to_notify = _deque(_islice(all_waiters, n))
  File "C:\Users\mick\programming\git\nvda\source\comtypesMonkeyPatches.py", line 101, in newCpbDel
    _cpbDel(self)
  File "C:\Users\mick\programming\git\nvda\include\comtypes\comtypes\__init__.py", line 918, in __del__
    self.Release()
  File "C:\Users\mick\programming\git\nvda\include\comtypes\comtypes\__init__.py", line 1172, in Release
    return self.__com_Release()
  File "C:\Users\mick\programming\git\nvda\source\comtypesMonkeyPatches.py", line 26, in __call__
    return super().__call__(*args,**kwargs)
```

At the same time, NVDA also deadlocks in its keydown hook, again in Queue.put on the same queue:
```
Python stack for thread 7632:
  File "C:\Program Files (x86)\Python37-32\lib\threading.py", line 890, in _bootstrap
    self._bootstrap_inner()
  File "C:\Program Files (x86)\Python37-32\lib\threading.py", line 926, in _bootstrap_inner
    self.run()
  File "C:\Program Files (x86)\Python37-32\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\mick\programming\git\nvda\source\winInputHook.py", line 79, in hookThreadFunc
    while windll.user32.GetMessageW(byref(msg),None,0,0):
  File "C:\Users\mick\programming\git\nvda\source\winInputHook.py", line 54, in keyboardHook
    if not keyDownCallback(kbd.vkCode,kbd.scanCode,bool(kbd.flags&LLKHF_EXTENDED),bool(kbd.flags&LLKHF_INJECTED)):
  File "C:\Users\mick\programming\git\nvda\source\keyboardHandler.py", line 199, in internal_keyDownEvent
    inputCore.manager.executeGesture(gesture)
  File "C:\Users\mick\programming\git\nvda\source\inputCore.py", line 442, in executeGesture
    queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech)
  File "C:\Users\mick\programming\git\nvda\source\queueHandler.py", line 37, in queueFunction
    queue.put_nowait((func,args,kwargs))
  File "C:\Program Files (x86)\Python37-32\lib\queue.py", line 190, in put_nowait
    return self.put(item, block=False)
  File "C:\Program Files (x86)\Python37-32\lib\queue.py", line 132, in put
    with self.not_full:
  File "C:\Program Files (x86)\Python37-32\lib\threading.py", line 241, in __enter__
    return self._lock.__enter__()
```
 
And finally also at the same time in NVDA's main thread:
```
Python stack for thread 1836:
  File "source/nvda.pyw", line 215, in <module>
  File "C:\Users\mick\programming\git\nvda\source\core.py", line 545, in main
    app.MainLoop()
  File "C:\Users\mick\programming\git\nvda\include\wxPython\wx\core.py", line 2134, in MainLoop
    rv = wx.PyApp.MainLoop(self)
  File "C:\Users\mick\programming\git\nvda\source\gui\__init__.py", line 1049, in Notify
    self.run()
  File "C:\Users\mick\programming\git\nvda\source\core.py", line 515, in run
    queueHandler.pumpAll()
  File "C:\Users\mick\programming\git\nvda\source\queueHandler.py", line 83, in pumpAll
    flushQueue(eventQueue)
  File "C:\Users\mick\programming\git\nvda\source\queueHandler.py", line 46, in flushQueue
    if not queue.empty():
  File "C:\Program Files (x86)\Python37-32\lib\queue.py", line 107, in empty
    with self.mutex:
```

Python issue 14976: https://bugs.python.org/issue14976
Suggests that ```__del__``` can run in Queue.put. 

To work around this, where ever we only need an unbounded queue with no task management (such as our main event queue) we should use queue.SimpleQueue instead, as it does not allow the garbage collector or other signals to run inside put.


Discussion:
A possibly related stack emailed by a user, generated when using Microsoft Word with a Focus braille display:
```
WARNING - watchdog._watcher (20:05:00.458) - watchdog (7672):
Core frozen in stack:
  File "nvda.pyw", line 215, in <module>
  File "core.pyc", line 545, in main
  File "wx\core.pyc", line 2134, in MainLoop
  File "gui\__init__.pyc", line 1030, in Notify
  File "core.pyc", line 514, in run
  File "IAccessibleHandler.pyc", line 898, in pumpAll
  File "IAccessibleHandler.pyc", line 775, in processForegroundWinEvent
  File "eventHandler.pyc", line 43, in queueEvent
  File "queueHandler.pyc", line 37, in queueFunction
  File "queue.pyc", line 190, in put_nowait
  File "queue.pyc", line 132, in put
  File "threading.pyc", line 241, in __enter__
```



Resolving patch:
diff --git a/source/NVDAHelper.py b/source/NVDAHelper.py
--- a/source/NVDAHelper.py
+++ b/source/NVDAHelper.py
@@ -398,7 +398,14 @@ def nvdaControllerInternal_inputLangChangeNotify(threadID,hkl,layoutString):
 def nvdaControllerInternal_typedCharacterNotify(threadID,ch):
 	focus=api.getFocusObject()
 	if focus.windowClassName!="ConsoleWindowClass":
-		eventHandler.queueEvent("typedCharacter",focus,ch=ch)
+		# Manually queue a call to executeEvent rather than using queueEvent,
+		# As Currently queueEvent uses a lock and there is a small chance it could deadlock
+		# If garbage collection within the lock
+		# caused a COM object from the same thread as the typed character to be released.
+		queueHandler.queueFunction(
+			queueHandler.eventQueue,
+			eventHandler.executeEvent, "typedCharacter", focus, ch=ch
+		)
 	return 0
 
 @WINFUNCTYPE(c_long, c_int, c_int)
diff --git a/source/queueHandler.py b/source/queueHandler.py
--- a/source/queueHandler.py
+++ b/source/queueHandler.py
@@ -5,14 +5,19 @@
 #See the file COPYING for more details.
 
 import types
-from queue import Queue
+from queue import SimpleQueue
 import globalVars
 from logHandler import log
 import watchdog
 import core
 
-eventQueue=Queue()
-eventQueue.__name__="eventQueue"
+# A queue for calls that should be made on NVDA's main thread
+# #11369: We use SimpleQueue rather than Queue here
+# as SimpleQueue is very light-weight, does not use locks
+# and ensures that garbage collection won't unexpectedly happen in the middle of queuing something
+# Which may cause a deadlock.
+eventQueue = SimpleQueue()
+
 generators={}
 lastGeneratorObjID=0
 


Test patch: