Skip to content

Commit

Permalink
output/alsa: don't call snd_pcm_drain() if nothing was written
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MaxKellermann committed Nov 16, 2018
1 parent 04f6322 commit 4cdcaa8
Showing 1 changed file with 21 additions and 1 deletion.
22 changes: 21 additions & 1 deletion src/output/plugins/AlsaOutputPlugin.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -305,9 +315,11 @@ class AlsaOutput final

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;
}
Expand Down Expand Up @@ -673,6 +685,7 @@ AlsaOutput::Open(AudioFormat &audio_format)

active = false;
must_prepare = false;
written = false;
error = {};
}

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -914,6 +933,7 @@ try {

if (must_prepare) {
must_prepare = false;
written = false;

int err = snd_pcm_prepare(pcm);
if (err < 0)
Expand Down

0 comments on commit 4cdcaa8

Please sign in to comment.