Loading services/core/java/com/android/server/pm/StagingManager.java +33 −1 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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++) { Loading Loading @@ -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"); } Loading @@ -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); Loading services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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 Loading Loading
services/core/java/com/android/server/pm/StagingManager.java +33 −1 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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++) { Loading Loading @@ -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"); } Loading @@ -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); Loading
services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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 Loading