AudioFlinger: Update Track / Thread mutex handling
Prevent deadlock when removing track from active tracks.
Test: atest CtsMediaAudioTestCases (including AudioPlaybackCaptureTest)
Test: Spatializer playback.
Test: Duplicating playback BT/Speaker.
Bug: 314229445
Merged-In: I802759430cec782cdefba89ca2bbafb9af67588c
Change-Id: I802759430cec782cdefba89ca2bbafb9af67588c
diff --git a/services/audioflinger/IAfThread.h b/services/audioflinger/IAfThread.h
index 7084be9..46a67e8 100644
--- a/services/audioflinger/IAfThread.h
+++ b/services/audioflinger/IAfThread.h
@@ -386,6 +386,12 @@
const effect_uuid_t* type, bool suspend, audio_session_t sessionId)
REQUIRES(mutex()) = 0;
+ // Wait while the Thread is busy. This is done to ensure that
+ // the Thread is not busy releasing the Tracks, during which the Thread mutex
+ // may be temporarily unlocked. Some Track methods will use this method to
+ // avoid races.
+ virtual void waitWhileThreadBusy_l(audio_utils::unique_lock& ul)
+ REQUIRES(mutex()) = 0;
// Dynamic cast to derived interface
virtual sp<IAfDirectOutputThread> asIAfDirectOutputThread() { return nullptr; }
virtual sp<IAfDuplicatingThread> asIAfDuplicatingThread() { return nullptr; }
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index d61621a..ddef7f3 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -2849,6 +2849,8 @@
// effectively get the latency it requested.
if (track->isExternalTrack()) {
IAfTrackBase::track_state state = track->state();
+ // Because the track is not on the ActiveTracks,
+ // at this point, only the TrackHandle will be adding the track.
mutex().unlock();
status = AudioSystem::startOutput(track->portId());
mutex().lock();
@@ -2929,7 +2931,12 @@
track->setResetDone(false);
track->resetPresentationComplete();
+
+ // Do not release the ThreadBase mutex after the track is added to mActiveTracks unless
+ // all key changes are complete. It is possible that the threadLoop will begin
+ // processing the added track immediately after the ThreadBase mutex is released.
mActiveTracks.add(track);
+
if (chain != 0) {
ALOGV("addTrack_l() starting track on chain %p for session %d", chain.get(),
track->sessionId());
@@ -4704,8 +4711,12 @@
void PlaybackThread::removeTracks_l(const Vector<sp<IAfTrack>>& tracksToRemove)
NO_THREAD_SAFETY_ANALYSIS // release and re-acquire mutex()
{
+ if (tracksToRemove.empty()) return;
+
+ // Block all incoming TrackHandle requests until we are finished with the release.
+ setThreadBusy_l(true);
+
for (const auto& track : tracksToRemove) {
- mActiveTracks.remove(track);
ALOGV("%s(%d): removing track on session %d", __func__, track->id(), track->sessionId());
sp<IAfEffectChain> chain = getEffectChain_l(track->sessionId());
if (chain != 0) {
@@ -4713,17 +4724,16 @@
__func__, track->id(), chain.get(), track->sessionId());
chain->decActiveTrackCnt();
}
+
// If an external client track, inform APM we're no longer active, and remove if needed.
- // We do this under lock so that the state is consistent if the Track is destroyed.
+ // Since the track is active, we do it here instead of TrackBase::destroy().
if (track->isExternalTrack()) {
+ mutex().unlock();
AudioSystem::stopOutput(track->portId());
if (track->isTerminated()) {
AudioSystem::releaseOutput(track->portId());
}
- }
- if (track->isTerminated()) {
- // remove from our tracks vector
- removeTrack_l(track);
+ mutex().lock();
}
if (mHapticChannelCount > 0 &&
((track->channelMask() & AUDIO_CHANNEL_HAPTIC_ALL) != AUDIO_CHANNEL_NONE
@@ -4740,7 +4750,24 @@
chain->setHapticIntensity_l(track->id(), os::HapticScale::MUTE);
}
}
+
+ // Under lock, the track is removed from the active tracks list.
+ //
+ // Once the track is no longer active, the TrackHandle may directly
+ // modify it as the threadLoop() is no longer responsible for its maintenance.
+ // Do not modify the track from threadLoop after the mutex is unlocked
+ // if it is not active.
+ mActiveTracks.remove(track);
+
+ if (track->isTerminated()) {
+ // remove from our tracks vector
+ removeTrack_l(track);
+ }
}
+
+ // Allow incoming TrackHandle requests. We still hold the mutex,
+ // so pending TrackHandle requests will occur after we unlock it.
+ setThreadBusy_l(false);
}
status_t PlaybackThread::getTimestamp_l(AudioTimestamp& timestamp)
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 21134a2..ddf0669 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -599,6 +599,35 @@
// check if some effects must be suspended when an effect chain is added
void checkSuspendOnAddEffectChain_l(const sp<IAfEffectChain>& chain) REQUIRES(mutex());
+ /**
+ * waitWhileThreadBusy_l() serves as a mutex gate, which does not allow
+ * progress beyond the method while the PlaybackThread is busy (see setThreadBusy_l()).
+ * During the wait, the ThreadBase_Mutex is temporarily unlocked.
+ *
+ * This implementation uses a condition variable. Alternative methods to gate
+ * the thread may use a second mutex (i.e. entry based on scoped_lock(mutex, gating_mutex)),
+ * but those have less flexibility and more lock order issues.
+ *
+ * Current usage by Track::destroy(), Track::start(), Track::stop(), Track::pause(),
+ * and Track::flush() block this way, and the primary caller is through TrackHandle
+ * with no other mutexes held.
+ *
+ * Special tracks like PatchTrack and OutputTrack may also hold the another thread's
+ * ThreadBase_Mutex during this time. No other mutex is held.
+ */
+
+ void waitWhileThreadBusy_l(audio_utils::unique_lock& ul) final REQUIRES(mutex()) {
+ // the wait returns immediately if the predicate is satisfied.
+ mThreadBusyCv.wait(ul, [&]{ return mThreadBusy == false;});
+ }
+
+ void setThreadBusy_l(bool busy) REQUIRES(mutex()) {
+ if (busy == mThreadBusy) return;
+ mThreadBusy = busy;
+ if (busy == true) return; // no need to wake threads if we become busy.
+ mThreadBusyCv.notify_all();
+ }
+
// sends the metadata of the active tracks to the HAL
struct MetadataUpdate {
std::vector<playback_track_metadata_v7_t> playbackMetadataUpdate;
@@ -641,6 +670,13 @@
ThreadMetrics mThreadMetrics;
const bool mIsOut;
+ // mThreadBusy is checked under the ThreadBase_Mutex to ensure that
+ // TrackHandle operations do not proceed while the ThreadBase is busy
+ // with the track. mThreadBusy is only true if the track is active.
+ //
+ bool mThreadBusy = false; // GUARDED_BY(ThreadBase_Mutex) but read in lambda.
+ audio_utils::condition_variable mThreadBusyCv;
+
// updated by PlaybackThread::readOutputParameters_l() or
// RecordThread::readInputParameters_l()
uint32_t mSampleRate;
@@ -839,7 +875,7 @@
SimpleLog mLocalLog; // locked internally
-private:
+ private:
void dumpBase_l(int fd, const Vector<String16>& args) REQUIRES(mutex());
void dumpEffectChains_l(int fd, const Vector<String16>& args) REQUIRES(mutex());
};
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index f18e69b..4e82173 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -890,12 +890,17 @@
bool wasActive = false;
const sp<IAfThreadBase> thread = mThread.promote();
if (thread != 0) {
- audio_utils::lock_guard _l(thread->mutex());
+ audio_utils::unique_lock ul(thread->mutex());
+ thread->waitWhileThreadBusy_l(ul);
+
auto* const playbackThread = thread->asIAfPlaybackThread().get();
wasActive = playbackThread->destroyTrack_l(this);
forEachTeePatchTrack_l([](const auto& patchTrack) { patchTrack->destroy(); });
}
if (isExternalTrack() && !wasActive) {
+ // If the track is not active, the TrackHandle is responsible for
+ // releasing the port id, not the ThreadBase::threadLoop().
+ // At this point, there is no concurrency issue as the track is going away.
AudioSystem::releaseOutput(mPortId);
}
}
@@ -1187,7 +1192,9 @@
return PERMISSION_DENIED;
}
}
- audio_utils::lock_guard _lth(thread->mutex());
+ audio_utils::unique_lock ul(thread->mutex());
+ thread->waitWhileThreadBusy_l(ul);
+
track_state state = mState;
// here the track could be either new, or restarted
// in both cases "unstop" the track
@@ -1312,7 +1319,9 @@
ALOGV("%s(%d): calling pid %d", __func__, mId, IPCThreadState::self()->getCallingPid());
const sp<IAfThreadBase> thread = mThread.promote();
if (thread != 0) {
- audio_utils::lock_guard _l(thread->mutex());
+ audio_utils::unique_lock ul(thread->mutex());
+ thread->waitWhileThreadBusy_l(ul);
+
track_state state = mState;
if (state == RESUMING || state == ACTIVE || state == PAUSING || state == PAUSED) {
// If the track is not active (PAUSED and buffers full), flush buffers
@@ -1347,7 +1356,9 @@
ALOGV("%s(%d): calling pid %d", __func__, mId, IPCThreadState::self()->getCallingPid());
const sp<IAfThreadBase> thread = mThread.promote();
if (thread != 0) {
- audio_utils::lock_guard _l(thread->mutex());
+ audio_utils::unique_lock ul(thread->mutex());
+ thread->waitWhileThreadBusy_l(ul);
+
auto* const playbackThread = thread->asIAfPlaybackThread().get();
switch (mState) {
case STOPPING_1:
@@ -1384,7 +1395,9 @@
ALOGV("%s(%d)", __func__, mId);
const sp<IAfThreadBase> thread = mThread.promote();
if (thread != 0) {
- audio_utils::lock_guard _l(thread->mutex());
+ audio_utils::unique_lock ul(thread->mutex());
+ thread->waitWhileThreadBusy_l(ul);
+
auto* const playbackThread = thread->asIAfPlaybackThread().get();
// Flush the ring buffer now if the track is not active in the PlaybackThread.