From f85d4d28d1e9c6e6079424d5c599102348b7d9f6 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Wed, 29 Mar 2017 20:12:14 +0200
Subject: [PATCH] output/alsa: work around dmix non-blocking snd_pcm_drain()
 bug

See code comment.  Bug was reported against MPD, but it's really an
alsa-lib bug.

 https://bugs.musicpd.org/view.php?id=4662
---
 src/output/plugins/AlsaOutputPlugin.cxx | 36 +++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index b93a5362b..6b7f9a7f3 100644
--- a/src/output/plugins/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "AlsaOutputPlugin.hxx"
 #include "lib/alsa/NonBlock.hxx"
+#include "lib/alsa/Version.hxx"
 #include "../OutputAPI.hxx"
 #include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
@@ -107,6 +108,17 @@ class AlsaOutput final
 	 */
 	snd_pcm_uframes_t period_frames;
 
+	/**
+	 * Is this a buggy alsa-lib version, which needs a workaround
+	 * for the snd_pcm_drain() bug always returning -EAGAIN?  See
+	 * alsa-lib commits fdc898d41135 and e4377b16454f for details.
+	 * This bug was fixed in alsa-lib version 1.1.4.
+	 *
+	 * The workaround is to re-enable blocking mode for the
+	 * snd_pcm_drain() call.
+	 */
+	bool work_around_drain_bug;
+
 	/**
 	 * After Open(), has this output been activated by a Play()
 	 * command?
@@ -988,6 +1000,19 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, PcmExport::Params &params)
 #endif
 }
 
+static constexpr bool
+MaybeDmix(snd_pcm_type_t type)
+{
+	return type == SND_PCM_TYPE_DMIX || type == SND_PCM_TYPE_PLUG;
+}
+
+gcc_pure
+static bool
+MaybeDmix(snd_pcm_t *pcm)
+{
+	return MaybeDmix(snd_pcm_type(pcm));
+}
+
 inline void
 AlsaOutput::Open(AudioFormat &audio_format)
 {
@@ -1012,6 +1037,9 @@ AlsaOutput::Open(AudioFormat &audio_format)
 							  GetDevice()));
 	}
 
+	work_around_drain_bug = MaybeDmix(pcm) &&
+		GetRuntimeAlsaVersion() < MakeAlsaVersion(1, 1, 4);
+
 	snd_pcm_nonblock(pcm, 1);
 
 #ifdef ENABLE_DSD
@@ -1117,6 +1145,14 @@ AlsaOutput::DrainInternal()
 	}
 
 	/* .. and finally drain the ALSA hardware buffer */
+
+	if (work_around_drain_bug) {
+		snd_pcm_nonblock(pcm, 0);
+		bool result = snd_pcm_drain(pcm) != -EAGAIN;
+		snd_pcm_nonblock(pcm, 1);
+		return result;
+	}
+
 	return snd_pcm_drain(pcm) != -EAGAIN;
 }