Fixed a deadlock that could occur if an IOutput blocks until buffers are freed in Stop().

This commit is contained in:
Casey Langen 2016-06-29 01:56:01 -07:00
parent 569c3df3d1
commit 9c9666da9d
3 changed files with 55 additions and 30 deletions

View File

@ -69,6 +69,7 @@ size_t countBuffersWithProvider(
} }
void CoreAudioOut::NotifyBufferCompleted(BufferContext *context) { void CoreAudioOut::NotifyBufferCompleted(BufferContext *context) {
{
boost::recursive_mutex::scoped_lock lock(this->mutex); boost::recursive_mutex::scoped_lock lock(this->mutex);
auto it = this->buffers.begin(); auto it = this->buffers.begin();
@ -78,6 +79,7 @@ void CoreAudioOut::NotifyBufferCompleted(BufferContext *context) {
} }
++it; ++it;
} }
}
context->provider->OnBufferProcessed(context->buffer); context->provider->OnBufferProcessed(context->buffer);
delete context; delete context;

View File

@ -46,7 +46,7 @@ static std::string TAG = "Transport";
#define RESET_NEXT_PLAYER() \ #define RESET_NEXT_PLAYER() \
delete this->nextPlayer; \ delete this->nextPlayer; \
this->nextPlayer = NULL; this->nextPlayer = nullptr;
#define DEFER(x, y) \ #define DEFER(x, y) \
{ \ { \
@ -65,7 +65,7 @@ static void deletePlayer(Player* p) {
GaplessTransport::GaplessTransport() GaplessTransport::GaplessTransport()
: volume(1.0) : volume(1.0)
, state(PlaybackStopped) , state(PlaybackStopped)
, nextPlayer(NULL) , nextPlayer(nullptr)
, nextCanStart(false) { , nextCanStart(false) {
this->output = Player::CreateDefaultOutput(); this->output = Player::CreateDefaultOutput();
} }
@ -103,43 +103,50 @@ void GaplessTransport::Start(const std::string& url) {
void GaplessTransport::StartWithPlayer(Player* newPlayer) { void GaplessTransport::StartWithPlayer(Player* newPlayer) {
if (newPlayer) { if (newPlayer) {
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
bool playingNext = (newPlayer == nextPlayer);
if (newPlayer != nextPlayer) {
delete nextPlayer;
}
this->nextPlayer = NULL;
/* first argument suppresses the "Stop" event from getting triggered,
the second param is used for gapless playback -- we won't stop the output
and will allow pending buffers to finish */
this->Stop(true, !playingNext);
this->SetNextCanStart(false);
newPlayer->PlaybackStarted.connect(this, &GaplessTransport::OnPlaybackStarted); newPlayer->PlaybackStarted.connect(this, &GaplessTransport::OnPlaybackStarted);
newPlayer->PlaybackAlmostEnded.connect(this, &GaplessTransport::OnPlaybackAlmostEnded); newPlayer->PlaybackAlmostEnded.connect(this, &GaplessTransport::OnPlaybackAlmostEnded);
newPlayer->PlaybackFinished.connect(this, &GaplessTransport::OnPlaybackFinished); newPlayer->PlaybackFinished.connect(this, &GaplessTransport::OnPlaybackFinished);
newPlayer->PlaybackError.connect(this, &GaplessTransport::OnPlaybackError); newPlayer->PlaybackError.connect(this, &GaplessTransport::OnPlaybackError);
musik::debug::info(TAG, "play()"); bool playingNext = false;
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
playingNext = (newPlayer == nextPlayer);
if (newPlayer != nextPlayer) {
delete nextPlayer;
}
this->nextPlayer = nullptr;
this->active.push_back(newPlayer); this->active.push_back(newPlayer);
}
/* first argument suppresses the "Stop" event from getting triggered,
the second param is used for gapless playback -- we won't stop the output
and will allow pending buffers to finish if we're not automatically
playing the next track. note we do this outside of critical section so
outputs *can* stop buffers immediately, and not to worry about causing a
deadlock. */
this->StopInternal(true, !playingNext, newPlayer);
this->SetNextCanStart(false);
this->output->Resume(); this->output->Resume();
newPlayer->Play(); newPlayer->Play();
} musik::debug::info(TAG, "play()");
this->RaiseStreamEvent(ITransport::StreamScheduled, newPlayer); this->RaiseStreamEvent(ITransport::StreamScheduled, newPlayer);
} }
} }
void GaplessTransport::Stop() { void GaplessTransport::Stop() {
this->Stop(false, true); this->StopInternal(false, true);
} }
void GaplessTransport::Stop(bool suppressStopEvent, bool stopOutput) { void GaplessTransport::StopInternal(
bool suppressStopEvent,
bool stopOutput,
Player* exclude)
{
musik::debug::info(TAG, "stop"); musik::debug::info(TAG, "stop");
/* if we stop the output, we kill all of the Players immediately. /* if we stop the output, we kill all of the Players immediately.
@ -151,7 +158,18 @@ void GaplessTransport::Stop(bool suppressStopEvent, bool stopOutput) {
{ {
boost::recursive_mutex::scoped_lock lock(this->stateMutex); boost::recursive_mutex::scoped_lock lock(this->stateMutex);
RESET_NEXT_PLAYER(); RESET_NEXT_PLAYER();
std::swap(toDelete, this->active);
/* move all but the excluded player to the toDelete set. */
auto it = this->active.begin();
while (it != this->active.end()) {
if (*it != exclude) {
toDelete.push_back(*it);
it = this->active.erase(it);
}
else {
++it;
}
}
} }
/* delete these in the background to avoid deadlock in some cases /* delete these in the background to avoid deadlock in some cases

View File

@ -67,7 +67,12 @@ namespace musik { namespace core { namespace audio {
private: private:
void StartWithPlayer(Player* player); void StartWithPlayer(Player* player);
void Stop(bool suppressStopEvent, bool stopOutput);
void StopInternal(
bool suppressStopEvent,
bool stopOutput,
Player* exclude = nullptr);
void RemoveActive(Player* player); void RemoveActive(Player* player);
void DeletePlayers(std::list<Player*> players); void DeletePlayers(std::list<Player*> players);
void SetNextCanStart(bool nextCanStart); void SetNextCanStart(bool nextCanStart);