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

Commit 61416b00 authored by Jooyung Han's avatar Jooyung Han
Browse files

StagingManager: notify failed sessions to apexd

StagingManager.restoreSessions() needs to handle inconsistency between
StagedSession and ApexSession. For example, when an ApexSession is in
"impossible state" (e.g. STAGED or VERIFIED), corresponding
StagedSession is marked as failed. This leads to yet another
inconsistency: ApexSession becomes "dangling" or "unknown" because the
StagedSession is destroyed.

These ApexSessions ("impossible state" or "dangling") won't be removed
and remain on a device forever.

In this commit, StagingManager notifies the "failure" to apexd by
calling abortStagedSession(id), which destroys ApexSessions.

Bug: 425478146
Flag: EXEMPT bug fix
Test: atest StagingManagerTest
Change-Id: Ibe9f949bc6d1bcdabf3f9102de8b98f069408350
parent 84200a08
Loading
Loading
Loading
Loading
+33 −1
Original line number Diff line number Diff line
@@ -665,6 +665,26 @@ public class StagingManager {
                    + "fs-checkpoint support");
        }

        // Collect unknown/dangling APEX sessions before handleNonReadyAndDestroyedSessions(),
        // which mutates |sessions|.
        final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
        List<Integer> sessionIdsToBeAborted = new ArrayList<>();

        for (int index = 0; index < apexSessions.size(); index++) {
            int sessionId = apexSessions.keyAt(index);
            // check if StagingManager knows this APEX session.
            boolean stagedSessionFound = false;
            for (int sessionIndex = 0; sessionIndex < sessions.size(); sessionIndex++) {
                if (sessions.get(sessionIndex).sessionId() == sessionId) {
                    stagedSessionFound = true;
                    break;
                }
            }
            if (!stagedSessionFound) {
                sessionIdsToBeAborted.add(sessionId);
            }
        }

        // Do a set of quick checks before resuming individual sessions:
        //   1. Schedule a pre-reboot verification for non-ready sessions.
        //   2. Abandon destroyed sessions.
@@ -672,7 +692,6 @@ public class StagingManager {

        //   3. Check state of apex sessions is consistent. All non-applied sessions will be marked
        //      as failed.
        final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
        boolean hasFailedApexSession = false;
        boolean hasAppliedApexSession = false;
        for (int i = 0; i < sessions.size(); i++) {
@@ -709,12 +728,14 @@ public class StagingManager {
                // Apexd did not apply the session for some unknown reason. There is no guarantee
                // that apexd will install it next time. Safer to proactively mark it as failed.
                hasFailedApexSession = true;
                sessionIdsToBeAborted.add(session.sessionId());
                session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
                        "Staged session " + session.sessionId() + " at boot didn't activate nor "
                        + "fail. Marking it as failed anyway.");
            } else {
                Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
                hasFailedApexSession = true;
                sessionIdsToBeAborted.add(session.sessionId());
                session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
                        "Impossible state");
            }
@@ -738,9 +759,20 @@ public class StagingManager {
                session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
                        "Another apex session failed");
            }
            // And make sure to keep failures in sync. abortStagedSession(id) will destroy the
            // failed APEX sessions.
            for (int index = 0; index < sessionIdsToBeAborted.size(); index++) {
                mApexManager.abortStagedSession(sessionIdsToBeAborted.get(index));
            }
            return;
        }

        // At this point, there's no failed APEX sessions. In case there's dangling APEX sessions,
        // abort them first and then continue to resume successful sessions.
        for (int index = 0; index < sessionIdsToBeAborted.size(); index++) {
            mApexManager.abortStagedSession(sessionIdsToBeAborted.get(index));
        }

        // Time to resume sessions.
        for (int i = 0; i < sessions.size(); i++) {
            StagedSession session = sessions.get(i);
+21 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ import org.mockito.stubbing.Answer;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -211,6 +212,7 @@ public class StagingManagerTest {
        sessions.add(destroyedNonReadySession);
        sessions.add(regularApkSession);

        when(mApexManager.getSessions()).thenReturn(new SparseArray<>(0));
        mStagingManager.restoreSessions(sessions, false);

        assertThat(sessions).containsExactly(regularApkSession);
@@ -441,6 +443,8 @@ public class StagingManagerTest {

        // Validate checkpoint wasn't aborted.
        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
        // ApexManager should get notified failed sessions.
        verify(mApexManager, times(1)).abortStagedSession(eq(impossible.sessionId));

        assertThat(apexSession.getErrorCode())
                .isEqualTo(PackageManager.INSTALL_ACTIVATION_FAILED);
@@ -451,6 +455,23 @@ public class StagingManagerTest {
        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
    }

    @Test
    public void restoreSessions_surplusApexSessions_shouldBeAborted() throws Exception {
        ApexSessionInfo impossible  = new ApexSessionInfo();
        impossible.sessionId = 1543;

        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
        apexdSessions.put(1543, impossible);
        when(mApexManager.getSessions()).thenReturn(apexdSessions);

        mStagingManager.restoreSessions(Collections.emptyList(), false);

        // Validate checkpoint wasn't aborted.
        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
        // ApexManager should get notified failed sessions.
        verify(mApexManager, times(1)).abortStagedSession(eq(impossible.sessionId));
    }

    @Test
    public void getStagedApexInfos_validatePreConditions() throws Exception {
        // Invalid session: null session