diff options
author | Max Kellermann <max@musicpd.org> | 2018-11-16 12:49:31 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2018-11-16 12:49:37 +0100 |
commit | 4cdcaa8630952e4bb53bd319440863e6295f1782 (patch) | |
tree | 3a6f1682dd0da3c84e9a3d1182a008842b5319b2 /src/output | |
parent | 04f632296f74c95c132086d3158785c1d36093e7 (diff) |
output/alsa: don't call snd_pcm_drain() if nothing was written
Works around a problem where MPD goes into a busy loop because
snd_pcm_drain() always returns `-EAGAIN` without making any progress
(fixes #425).
This problem was triggered by snd_pcm_drain() after snd_pcm_cancel()
and snd_pcm_prepare(), but without submitting any data with
snd_pcm_writei().
I believe this is a kernel bug: in non-blocking mode, the kernel's
snd_pcm_drain() function returns early. In this mode, it only checks
whether snd_pcm_drain_done() has been called already, but
snd_pcm_drain_done() is never called if no data was submitted.
In blocking mode, the following `for` loop detects this condition, so
snd_pcm_drain_done() is not necessary, but without this extra check,
we get `-EAGAIN` forever.
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/plugins/AlsaOutputPlugin.cxx | 22 |
1 files changed, 21 insertions, 1 deletions
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 4cf0c3bd2..8f32cf8d7 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -147,6 +147,16 @@ class AlsaOutput final */ bool must_prepare; + /** + * Has snd_pcm_writei() been called successfully at least once + * since the PCM was prepared? + * + * This is necessary to work around a kernel bug which causes + * snd_pcm_drain() to return -EAGAIN forever in non-blocking + * mode if snd_pcm_writei() was never called. + */ + bool written; + bool drain; /** @@ -305,9 +315,11 @@ private: auto frames_written = snd_pcm_writei(pcm, period_buffer.GetHead(), period_buffer.GetFrames(out_frame_size)); - if (frames_written > 0) + if (frames_written > 0) { + written = true; period_buffer.ConsumeFrames(frames_written, out_frame_size); + } return frames_written; } @@ -673,6 +685,7 @@ AlsaOutput::Open(AudioFormat &audio_format) active = false; must_prepare = false; + written = false; error = {}; } @@ -705,6 +718,7 @@ AlsaOutput::Recover(int err) noexcept case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: period_buffer.Rewind(); + written = false; err = snd_pcm_prepare(pcm); break; case SND_PCM_STATE_DISCONNECTED: @@ -755,6 +769,11 @@ AlsaOutput::DrainInternal() return period_buffer.IsEmpty(); } + if (!written) + /* if nothing has ever been written to the PCM, we + don't need to drain it */ + return true; + /* .. and finally drain the ALSA hardware buffer */ int result; @@ -914,6 +933,7 @@ try { if (must_prepare) { must_prepare = false; + written = false; int err = snd_pcm_prepare(pcm); if (err < 0) |