output/alsa: implement Interrupt()
This allows canceling the blocking method LockWaitWriteAvailable(), and thus allows breaking free of misbehaving ALSA drivers, avoiding a MPD lockup. Closes https://github.com/MusicPlayerDaemon/MPD/issues/966
This commit is contained in:
parent
4cb5e69811
commit
4ad525d939
2
NEWS
2
NEWS
|
@ -1,4 +1,6 @@
|
||||||
ver 0.22.1 (not yet released)
|
ver 0.22.1 (not yet released)
|
||||||
|
* output
|
||||||
|
- alsa: don't deadlock when the ALSA driver is buggy
|
||||||
|
|
||||||
ver 0.22 (2020/09/23)
|
ver 0.22 (2020/09/23)
|
||||||
* protocol
|
* protocol
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "lib/alsa/PeriodBuffer.hxx"
|
#include "lib/alsa/PeriodBuffer.hxx"
|
||||||
#include "lib/alsa/Version.hxx"
|
#include "lib/alsa/Version.hxx"
|
||||||
#include "../OutputAPI.hxx"
|
#include "../OutputAPI.hxx"
|
||||||
|
#include "../Error.hxx"
|
||||||
#include "mixer/MixerList.hxx"
|
#include "mixer/MixerList.hxx"
|
||||||
#include "pcm/Export.hxx"
|
#include "pcm/Export.hxx"
|
||||||
#include "system/PeriodClock.hxx"
|
#include "system/PeriodClock.hxx"
|
||||||
|
@ -177,6 +178,15 @@ class AlsaOutput final
|
||||||
|
|
||||||
bool drain;
|
bool drain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Was Interrupt() called? This will unblock
|
||||||
|
* LockWaitWriteAvailable(). It will be reset by Cancel() and
|
||||||
|
* Pause(), as documented by the #AudioOutput interface.
|
||||||
|
*
|
||||||
|
* Only initialized while the output is open.
|
||||||
|
*/
|
||||||
|
bool interrupted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This buffer gets allocated after opening the ALSA device.
|
* This buffer gets allocated after opening the ALSA device.
|
||||||
* It contains silence samples, enough to fill one period (see
|
* It contains silence samples, enough to fill one period (see
|
||||||
|
@ -237,9 +247,12 @@ private:
|
||||||
void Open(AudioFormat &audio_format) override;
|
void Open(AudioFormat &audio_format) override;
|
||||||
void Close() noexcept override;
|
void Close() noexcept override;
|
||||||
|
|
||||||
|
void Interrupt() noexcept override;
|
||||||
|
|
||||||
size_t Play(const void *chunk, size_t size) override;
|
size_t Play(const void *chunk, size_t size) override;
|
||||||
void Drain() override;
|
void Drain() override;
|
||||||
void Cancel() noexcept override;
|
void Cancel() noexcept override;
|
||||||
|
bool Pause() noexcept override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the snd_pcm_t object which was opened by the caller.
|
* Set up the snd_pcm_t object which was opened by the caller.
|
||||||
|
@ -728,6 +741,7 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||||
out_frame_size = pcm_export->GetOutputFrameSize();
|
out_frame_size = pcm_export->GetOutputFrameSize();
|
||||||
|
|
||||||
drain = false;
|
drain = false;
|
||||||
|
interrupted = false;
|
||||||
|
|
||||||
size_t period_size = period_frames * out_frame_size;
|
size_t period_size = period_frames * out_frame_size;
|
||||||
ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(period_size * 4);
|
ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(period_size * 4);
|
||||||
|
@ -741,6 +755,18 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||||
error = {};
|
error = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AlsaOutput::Interrupt() noexcept
|
||||||
|
{
|
||||||
|
std::unique_lock<Mutex> lock(mutex);
|
||||||
|
|
||||||
|
/* the "interrupted" flag will prevent
|
||||||
|
LockWaitWriteAvailable() from actually waiting, and will
|
||||||
|
instead throw AudioOutputInterrupted */
|
||||||
|
interrupted = true;
|
||||||
|
cond.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
inline int
|
inline int
|
||||||
AlsaOutput::Recover(int err) noexcept
|
AlsaOutput::Recover(int err) noexcept
|
||||||
{
|
{
|
||||||
|
@ -912,6 +938,11 @@ AlsaOutput::CancelInternal() noexcept
|
||||||
void
|
void
|
||||||
AlsaOutput::Cancel() noexcept
|
AlsaOutput::Cancel() noexcept
|
||||||
{
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock<Mutex> lock(mutex);
|
||||||
|
interrupted = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!LockIsActive()) {
|
if (!LockIsActive()) {
|
||||||
/* early cancel, quick code path without thread
|
/* early cancel, quick code path without thread
|
||||||
synchronization */
|
synchronization */
|
||||||
|
@ -928,6 +959,17 @@ AlsaOutput::Cancel() noexcept
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
AlsaOutput::Pause() noexcept
|
||||||
|
{
|
||||||
|
std::unique_lock<Mutex> lock(mutex);
|
||||||
|
interrupted = false;
|
||||||
|
|
||||||
|
/* not implemented - this override exists only to reset the
|
||||||
|
"interrupted" flag */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AlsaOutput::Close() noexcept
|
AlsaOutput::Close() noexcept
|
||||||
{
|
{
|
||||||
|
@ -956,6 +998,11 @@ AlsaOutput::LockWaitWriteAvailable()
|
||||||
if (error)
|
if (error)
|
||||||
std::rethrow_exception(error);
|
std::rethrow_exception(error);
|
||||||
|
|
||||||
|
if (interrupted)
|
||||||
|
/* a CANCEL command is in flight - don't block
|
||||||
|
here */
|
||||||
|
throw AudioOutputInterrupted{};
|
||||||
|
|
||||||
size_t write_available = ring_buffer->write_available();
|
size_t write_available = ring_buffer->write_available();
|
||||||
if (write_available >= min_available) {
|
if (write_available >= min_available) {
|
||||||
/* reserve room for one extra block, just in
|
/* reserve room for one extra block, just in
|
||||||
|
|
Loading…
Reference in New Issue