summaryrefslogtreecommitdiff
path: root/src/output
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2018-11-16 12:49:31 +0100
committerMax Kellermann <max@musicpd.org>2018-11-16 12:49:37 +0100
commit4cdcaa8630952e4bb53bd319440863e6295f1782 (patch)
tree3a6f1682dd0da3c84e9a3d1182a008842b5319b2 /src/output
parent04f632296f74c95c132086d3158785c1d36093e7 (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.cxx22
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)