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

Commit 4ca33d65 authored by Weilin Xu's avatar Weilin Xu
Browse files

Fix deadlock due to callbacks in ProgramList

Copy callback member variables in ProgramList and call them after
releasing the lock of ProgramList, avoiding acquiring lock in
RadioAppService after already locking ProgramList, which causes
deadlook.

Bug: 193041795
Test: m -j
Test: atest android.hardware.radio.tests.functional
Test: atest com.android.server.broadcastradio.hal2
Change-Id: I196377ea030248a5d66a4db03ffb3bee4a70e633
parent d9d2fe5a
Loading
Loading
Loading
Loading
+34 −10
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -173,38 +172,63 @@ public final class ProgramList implements AutoCloseable {
        }
    }

    void apply(@NonNull Chunk chunk) {
    void apply(Chunk chunk) {
        List<ProgramSelector.Identifier> removedList = new ArrayList<>();
        List<ProgramSelector.Identifier> changedList = new ArrayList<>();
        List<ProgramList.ListCallback> listCallbacksCopied;
        List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
        synchronized (mLock) {
            if (mIsClosed) return;

            mIsComplete = false;
            listCallbacksCopied = new ArrayList<>(mListCallbacks);

            if (chunk.isPurge()) {
                new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
                for (ProgramSelector.Identifier id : mPrograms.keySet()) {
                    removeLocked(id, removedList);
                }
            }

            chunk.getRemoved().stream().forEach(id -> removeLocked(id));
            chunk.getModified().stream().forEach(info -> putLocked(info));
            chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
            chunk.getModified().stream().forEach(info -> putLocked(info, changedList));

            if (chunk.isComplete()) {
                mIsComplete = true;
                mOnCompleteListeners.forEach(cb -> cb.onComplete());
                onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
            }
        }

        for (int i = 0; i < removedList.size(); i++) {
            for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
                listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
            }
        }
        for (int i = 0; i < changedList.size(); i++) {
            for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
                listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
            }
        }
        if (chunk.isComplete()) {
            for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
                onCompleteListenersCopied.get(cbIndex).onComplete();
            }
        }
    }

    private void putLocked(@NonNull RadioManager.ProgramInfo value) {
    private void putLocked(RadioManager.ProgramInfo value,
            List<ProgramSelector.Identifier> changedIdentifierList) {
        ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
        mPrograms.put(Objects.requireNonNull(key), value);
        ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
        mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
        changedIdentifierList.add(sel);
    }

    private void removeLocked(@NonNull ProgramSelector.Identifier key) {
    private void removeLocked(ProgramSelector.Identifier key,
            List<ProgramSelector.Identifier> removedIdentifierList) {
        RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
        if (removed == null) return;
        ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
        mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
        removedIdentifierList.add(sel);
    }

    /**