Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 406619f4 authored by Ytai Ben-Tsvi's avatar Ytai Ben-Tsvi
Browse files

Avoid risk of deadlock between APM and sound trigger

Sound trigger calls into audio policy manager (APM) for the sake of
allocating and releasing audio sessions.
APM calls into sound trigger for the sake of notifying it of
external capture session start/end.

Both calls are essentially synchronous, i.e. required for the
completion of the operations that trigger them.
Since both services use coarse locks to protect their state, if both
those calls happen concurrently, a deadlock would result.

The fix is to invoke
AudioPolicyManager::{acquire/release}SoundTriggerSession()
outside of the critical section of SoundTriggerModule.

Bug: 148691451
Change-Id: I157367b9e43963e37344958e1647e9f6c29d5e6e
parent 54e0f0a3
Loading
Loading
Loading
Loading
+68 −40
Original line number Diff line number Diff line
@@ -248,6 +248,13 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
        @Override
        public int loadModel(@NonNull SoundModel model) {
            Log.d(TAG, String.format("loadModel(model=%s)", model));

            // We must do this outside the lock, to avoid possible deadlocks with the remote process
            // that provides the audio sessions, which may also be calling into us.
            SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
                    mAudioSessionProvider.acquireSession();

            try {
                synchronized (SoundTriggerModule.this) {
                    checkValid();
                    if (mNumLoadedModels == mProperties.maxSoundModels) {
@@ -255,15 +262,28 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
                                "Maximum number of models loaded.");
                    }
                    Model loadedModel = new Model();
                int result = loadedModel.load(model);
                    int result = loadedModel.load(model, audioSession);
                    ++mNumLoadedModels;
                    return result;
                }
            } catch (Exception e) {
                // We must do this outside the lock, to avoid possible deadlocks with the remote
                // process that provides the audio sessions, which may also be calling into us.
                mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                throw e;
            }
        }

        @Override
        public int loadPhraseModel(@NonNull PhraseSoundModel model) {
            Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));

            // We must do this outside the lock, to avoid possible deadlocks with the remote process
            // that provides the audio sessions, which may also be calling into us.
            SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
                    mAudioSessionProvider.acquireSession();

            try {
                synchronized (SoundTriggerModule.this) {
                    checkValid();
                    if (mNumLoadedModels == mProperties.maxSoundModels) {
@@ -271,21 +291,34 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
                                "Maximum number of models loaded.");
                    }
                    Model loadedModel = new Model();
                int result = loadedModel.load(model);
                    int result = loadedModel.load(model, audioSession);
                    ++mNumLoadedModels;
                    Log.d(TAG, String.format("loadPhraseModel()->%d", result));
                    return result;
                }
            } catch (Exception e) {
                // We must do this outside the lock, to avoid possible deadlocks with the remote
                // process that provides the audio sessions, which may also be calling into us.
                mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                throw e;
            }
        }

        @Override
        public void unloadModel(int modelHandle) {
            Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));

            int sessionId;

            synchronized (SoundTriggerModule.this) {
                checkValid();
                mLoadedModels.get(modelHandle).unload();
                sessionId = mLoadedModels.get(modelHandle).unload();
                --mNumLoadedModels;
            }

            // We must do this outside the lock, to avoid possible deadlocks with the remote process
            // that provides the audio sessions, which may also be calling into us.
            mAudioSessionProvider.releaseSession(sessionId);
        }

        @Override
@@ -413,45 +446,40 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
                SoundTriggerModule.this.notifyAll();
            }

            private int load(@NonNull SoundModel model) {
            private int load(@NonNull SoundModel model,
                    SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                mModelType = model.type;
                mSession = audioSession;
                ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);

                mSession = mAudioSessionProvider.acquireSession();
                try {
                mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
                } catch (Exception e) {
                    mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
                    throw e;
                }

                setState(ModelState.LOADED);
                mLoadedModels.put(mHandle, this);
                return mHandle;
            }

            private int load(@NonNull PhraseSoundModel model) {
            private int load(@NonNull PhraseSoundModel model,
                    SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                mModelType = model.common.type;
                mSession = audioSession;
                ISoundTriggerHw.PhraseSoundModel hidlModel =
                        ConversionUtil.aidl2hidlPhraseSoundModel(model);

                mSession = mAudioSessionProvider.acquireSession();
                try {
                mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
                } catch (Exception e) {
                    mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
                    throw e;
                }

                setState(ModelState.LOADED);
                mLoadedModels.put(mHandle, this);
                return mHandle;
            }

            private void unload() {
                mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
            /**
             * Unloads the model.
             * @return The audio session handle.
             */
            private int unload() {
                mHalService.unloadSoundModel(mHandle);
                mLoadedModels.remove(mHandle);
                return mSession.mSessionHandle;
            }

            private void startRecognition(@NonNull RecognitionConfig config) {