Closed Bug 1274479 Opened 4 years ago Closed 3 years ago

Crash in shutdownhang | stuck in winmm_stream_destroy and winmm_buffer_thread

Categories

(Core :: Audio/Video: cubeb, defect, P2)

Other Branch
x86
Windows 10
defect

Tracking

()

RESOLVED FIXED
mozilla51
Tracking Status
firefox51 --- fixed

People

(Reporter: alwu, Assigned: kinetik)

References

Details

(Keywords: crash)

Crash Data

Attachments

(1 file, 2 obsolete files)

This bug was filed from the Socorro interface and is 
report bp-38b3fd81-44ff-4454-9a8e-1e1812160519.
=============================================================

Thread 43
- winmm_buffer_thread call WaitForSingleObject

Thread 54
- winmm_stream_destroy call WaitForSingleObject

Maybe the hang is because both two functions are calling WaitForSingleObject at same time.
Blocks: 1267752
Rank: 15
Priority: -- → P1
Severity: critical → normal
Rank: 15 → 25
Priority: P1 → P2
This is showing up on Windows 7, Windows 10, and Windows XP.  For Vista+, we prefer the WASAPI backend over WinMM and for XP WinMM is the only option.

So we need to explain why the hang happens on XP and also why Vista+ machines are ending up suck in the WinMM background.

My theory, which I've tested and used to reproduce this hang on Windows 10, is that this is related to the initial absence (on Vista+) and eventual removal of the last viable audio output device.

Steps to reproduce on Vista+:
- Disable all audio output devices in the Sound control panel
- Play some audio media
- Notice that there's no audio
- Attach an output device (e.g. plug in a headset)
- Reload media and enjoy audio

At this point, cubeb_init (which is only called once) has already failed to initialize WASAPI and fallen back to WinMM.  When the media is reloaded, cubeb_stream_init is using the WinMM backend to allocate a stream.

This explains how Vista+ machines can end up here, now the explanation that covers all Windows, including XP:

Steps to reproduce on XP (or Vista+ with above steps included):
- While media is playing or paused, remove/disable the last viable audio output device
  (if media was playing, playback will "hang" because the audio clock stops advancing)
- Close tab containing media (or any other action that triggers audio stream destruction, e.g. seeking)

At this point, winmm_stream_destroy request (via waveOutReset) and then wait on the (old, now invalidated) audio device to free the audio buffers on stm->event.  This event will never be signaled, because the device no longer exists to free the buffers.

Debugging notes:
- This fallback behaviour is undesirable on Vista+, we'd prefer to use WASAPI when we can, which should be possible once a device has been attached.
- The playback "hang" on device removal with WinMM is undesirable, ideally we'd detect the device was removed quickly and error/shutdown the audio stream.
- winmm_stream_stop called before winmm_stream_destroy will return an error because waveOutPause returns MMSYSERR_NODRIVER, but AudioStream::Shutdown ignores the result of cubeb_stream_stop because it's about to destroy the stream.
- winmm_stream_destroy doesn't check the result of waveOutReset (I think it has a valid reason for ignoring at least some error codes, but perhaps we can detect the absence of a valid device here).
- winmm_stream_destroy may also be able to use a timeout on the WaitForSingleObject to avoid hanging here.
Assignee: nobody → kinetik
Status: NEW → ASSIGNED
(In reply to Matthew Gregan [:kinetik] from comment #3)
> - The playback "hang" on device removal with WinMM is undesirable, ideally
> we'd detect the device was removed quickly and error/shutdown the audio
> stream.

We can detect this, and cubeb is already returning an error, so the problem may be in one of the callers.  In this case, inside winmm_stream_get_position, waveOutGetPosition returns MMSYSERR_INVALHANDLE which results in returning CUBEB_ERROR which AudioStream::GetPosition turns into a return of -1.
Other stuff to do:
- change telemetry to track first-vs-subsequent cubeb_init attempts
  before all WASAPI failures turned into WinMM backends, now we can see real failures

(In reply to Matthew Gregan [:kinetik] from comment #3)
> - winmm_stream_destroy doesn't check the result of waveOutReset (I think it
> has a valid reason for ignoring at least some error codes, but perhaps we
> can detect the absence of a valid device here).

We used to simply assert(r == MMSYSERR_NOERROR) before bug 742154 (specifically changeset https://github.com/kinetiknz/cubeb/commit/1188ca3429acd752ca335ae0085c57354f55ec0b) started ignoring the result completely.

Despite waveOutReset having MMSYSERR_INVALHANDLE and MMSYSERR_NODRIVER as documented error returns, in the case of the current audio device being removed it returns MMSYSERROR_NOERROR, so we need a way to detect the device has disappeared on shutdown.  Possibly setting a flag when waveOutPause in winmm_stream_stop fails.
Attached patch wip (obsolete) — Splinter Review
Attached patch wip (obsolete) — Splinter Review
Attachment #8789676 - Attachment is obsolete: true
Attached file fix v1
Attachment #8789677 - Attachment is obsolete: true
Attachment #8789679 - Flags: review?(padenot)
https://hg.mozilla.org/mozilla-central/rev/db9b0546e3d4
Status: ASSIGNED → RESOLVED
Closed: 3 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla51
Attachment #8789679 - Flags: review?(padenot) → review+
Depends on: 1303083
You need to log in before you can comment on or make changes to this bug.