Fix deadlock between nvWave stop and feed (PR #11886)
Fixes #11591
## The background:
On at least one user's system, a deadlock seems to occur while using NVDA with the oneCore synth. This results in a
freeze and a stack dump by the watchdog.
Stacks to note, from [log](https://github.com/nvaccess/nvda/issues/11591#issuecomment-714629177):
Python stack for thread 3128 (Dummy-82):
File "synthDrivers\oneCore.pyc", line 365, in _callback
File "nvwave.pyc", line 326, in feed
File "nvwave.pyc", line 341, in _feedUnbuffered_handleErrors
File "nvwave.pyc", line 369, in _feedUnbuffered
File "nvwave.pyc", line 386, in sync
File "winKernel.pyc", line 225, in waitForSingleObject
Python stack for thread 9300 (MainThread):
File "nvda.pyw", line 215, in <module>
File "core.pyc", line 550, in main
File "wx\core.pyc", line 2134, in MainLoop
File "gui\__init__.pyc", line 1050, in Notify
File "core.pyc", line 520, in run
File "queueHandler.pyc", line 88, in pumpAll
File "queueHandler.pyc", line 55, in flushQueue
File "speech\__init__.pyc", line 146, in cancelSpeech
File "speech\manager.pyc", line 737, in cancel
File "synthDrivers\oneCore.pyc", line 209, in cancel
File "nvwave.pyc", line 473, in stop
File "nvwave.pyc", line 435, in _idleUnbuffered
Because I can't reproduce this, I'm having to reason about how the code got into this state. What seems to have happened
is that 'feed' and 'stop' have been called at the same time from the synth thread and the main thread respectively.
These have both progressed to sub-routines, 'sync' and '_idleUnbuffered' respectively. '_idleUnbuffered' is blocked
waiting to acquire the 'self._lock', and 'sync' has acquired the 'self._lock' mutex and is blocked by
'waitForSingleObject'.
'feed' has no locks until it calls into '_feedUnbuffered', however 'stop' does acquire locks that '_feedUnbuffered'
needs, then releases them and then calls to '_idleUnbuffered' which has contention with '_feedUnbuffered' again. It
seems to me that the only way to get into this situation is if 'stop' acquires the locks it needs first. It calls
'waveOutReset', then when it releases the locks and before they are acquired by '_idleUnbuffered' they are instead
acquired by '_feedUnbuffered'. This results in 'waveOutWrite' being called before progressing to 'sync' and waiting on
'waitForSingleObject'
It's still not entirely clear to me why this set of events causes a deadlock. My best guess is that on this particular
system, the 'whdr' (Wave Out Header) which includes 'dwFlags' that should have the 'WHDR_DONE' bit set when the
associated buffer has finished being played is not getting updated due to the call to reset. Thus the synth thread gets
stuck in a loop calling 'waitForSingleObject' and checking '_prev_whdr'.
## This change:
Because I'm not 100% sure of the cause of the problem, I have covered a few potentials:
- 'waitForSingleObject' now has a timeout, it then will check if 'nvWave' has been stopped before waiting again.
- When resetting the device, the event is explicitly signaled. The documentation for 'winm' isn't explicit about whether
this is handled.
- When feed is called, don't send data to the device if cancel has already been called.
## Relevant Info from Docs:
[When opening 'winm' device, the event signal args]
(https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/nf-mmeapi-waveoutopen):
> If fdwOpen contains the CALLBACK_EVENT flag, dwCallback is a handle to an event. The event is signaled whenever the
> state of the waveform buffer changes. The application can use WaitForSingleObject or WaitForMultipleObjects to wait
> for the event. When the event is signaled, you can get the current state of the waveform buffer by checking the
> dwFlags member of the WAVEHDR structure. (See waveOutPrepareHeader.)
[waveOutReset](https://docs.microsoft.com/en-us/windows/win32/api/mmeapi/nf-mmeapi-waveoutreset):
> The waveOutReset function stops playback on the given waveform-audio output device and resets the current position to
> zero. All pending playback buffers are marked as done (WHDR_DONE) and returned to the application.
## The future:
The locking logic for 'nvWave' is complicated, however given plans to re write 'nvWave' with modern Windows API's I
think we should hold off on doing more than we need to resolve this specific issue