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

Commit 2ea5291c authored by JW Wang's avatar JW Wang
Browse files

Fix up the threading model (1/n)

1. Add thread annotations
2. assert @WorkerThread methods really run on the worker thread
3. dispatch tasks to the worker thread where all state changes should
   happen

TODO: remove locks in the next CL.

Bug: 159390709
Test: atest RollbackTest StagedRollbackTest
Test: atest RollbackUnitTest RollbackStoreTest AppDataRollbackHelperTest

Change-Id: I1e5828b2408be6512be07820e0dce509e7518d7b
parent 98176cac
Loading
Loading
Loading
Loading
+148 −51
Original line number Diff line number Diff line
@@ -19,9 +19,11 @@ package com.android.server.rollback;
import static com.android.server.rollback.RollbackManagerServiceImpl.sendFailure;

import android.Manifest;
import android.annotation.AnyThread;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -33,6 +35,8 @@ import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.os.UserManager;
@@ -45,6 +49,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;

@@ -57,10 +62,23 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;


/**
 * Information about a rollback available for a set of atomically installed packages.
 *
 * Threading model:
 *
 * Each method falls into one of the 2 categories:
 * - @AnyThread annotates thread-safe methods.
 * - @WorkerThread annotates methods that should be called from the worker thread only.
 *
 * In production code, the constructor is called on the worker thread of
 * {@link RollbackManagerServiceImpl}. All method invocations should happen on this thread.
 * Violation of thread invariants will trigger exceptions. In the case of unit tests, it is up to
 * the tests to serialize all method calls to avoid race condition. No thread invariants are
 * enforced in this case.
 */
class Rollback {

@@ -182,6 +200,12 @@ class Rollback {
     */
    @NonNull private final SparseIntArray mExtensionVersions;

    /**
     * The worker thread on which all method invocations should happen. It might be null in the
     * case of unit tests where no thread invariants are enforced.
     */
    @Nullable private final Handler mHandler;

    /**
     * Constructs a new, empty Rollback instance.
     *
@@ -209,6 +233,7 @@ class Rollback {
        mTimestamp = Instant.now();
        mPackageSessionIds = packageSessionIds != null ? packageSessionIds : new int[0];
        mExtensionVersions = Objects.requireNonNull(extensionVersions);
        mHandler = Looper.myLooper() != null ? new Handler(Looper.myLooper()) : null;
    }

    Rollback(int rollbackId, File backupDir, int stagedSessionId, int userId,
@@ -238,11 +263,17 @@ class Rollback {
        // this field is not backward compatible. We won't fix b/120200473 until S to minimize the
        // impact.
        mPackageSessionIds = new int[0];
        mHandler = Looper.myLooper() != null ? new Handler(Looper.myLooper()) : null;
    }

    private void assertInWorkerThread() {
        Preconditions.checkState(mHandler == null || mHandler.getLooper().isCurrentThread());
    }

    /**
     * Whether the rollback is for rollback of a staged install.
     */
    @AnyThread
    boolean isStaged() {
        return info.isStaged();
    }
@@ -250,6 +281,7 @@ class Rollback {
    /**
     * Returns the directory in which rollback data should be stored.
     */
    @AnyThread
    File getBackupDir() {
        return mBackupDir;
    }
@@ -257,7 +289,9 @@ class Rollback {
    /**
     * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
     */
    @WorkerThread
    Instant getTimestamp() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mTimestamp;
        }
@@ -266,7 +300,9 @@ class Rollback {
    /**
     * Sets the time at which upgrade occurred.
     */
    @WorkerThread
    void setTimestamp(Instant timestamp) {
        assertInWorkerThread();
        synchronized (mLock) {
            mTimestamp = timestamp;
            RollbackStore.saveRollback(this);
@@ -277,6 +313,7 @@ class Rollback {
     * Returns the session ID for the staged session if this rollback data represents a staged
     * session, or {@code -1} otherwise.
     */
    @AnyThread
    int getStagedSessionId() {
        return mStagedSessionId;
    }
@@ -284,6 +321,7 @@ class Rollback {
    /**
     * Returns the ID of the user that performed the install with rollback enabled.
     */
    @AnyThread
    int getUserId() {
        return mUserId;
    }
@@ -292,6 +330,7 @@ class Rollback {
     * Returns the installer package name from the install session that enabled the rollback. In the
     * case that this is called on a rollback from an older version, returns the empty string.
     */
    @AnyThread
    @Nullable String getInstallerPackageName() {
        return mInstallerPackageName;
    }
@@ -300,6 +339,7 @@ class Rollback {
     * Returns the extension versions that were supported at the time that the rollback was created,
     * as a mapping from SdkVersion to ExtensionVersion.
     */
    @AnyThread
    SparseIntArray getExtensionVersions() {
        return mExtensionVersions;
    }
@@ -307,7 +347,9 @@ class Rollback {
    /**
     * Returns true if the rollback is in the ENABLING state.
     */
    @WorkerThread
    boolean isEnabling() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mState == ROLLBACK_STATE_ENABLING;
        }
@@ -316,7 +358,9 @@ class Rollback {
    /**
     * Returns true if the rollback is in the AVAILABLE state.
     */
    @WorkerThread
    boolean isAvailable() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mState == ROLLBACK_STATE_AVAILABLE;
        }
@@ -325,7 +369,9 @@ class Rollback {
    /**
     * Returns true if the rollback is in the COMMITTED state.
     */
    @WorkerThread
    boolean isCommitted() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mState == ROLLBACK_STATE_COMMITTED;
        }
@@ -334,7 +380,9 @@ class Rollback {
    /**
     * Returns true if the rollback is in the DELETED state.
     */
    @WorkerThread
    boolean isDeleted() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mState == ROLLBACK_STATE_DELETED;
        }
@@ -343,7 +391,9 @@ class Rollback {
    /**
     * Saves this rollback to persistent storage.
     */
    @WorkerThread
    void saveRollback() {
        assertInWorkerThread();
        synchronized (mLock) {
            RollbackStore.saveRollback(this);
        }
@@ -354,8 +404,10 @@ class Rollback {
     *
     * @return boolean True if the rollback was enabled successfully for the specified package.
     */
    @WorkerThread
    boolean enableForPackage(String packageName, long newVersion, long installedVersion,
            boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy) {
        assertInWorkerThread();
        try {
            RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
            if (!ArrayUtils.isEmpty(splitSourceDirs)) {
@@ -386,8 +438,10 @@ class Rollback {
     *
     * @return boolean True if the rollback was enabled successfully for the specified package.
     */
    @WorkerThread
    boolean enableForPackageInApex(String packageName, long installedVersion,
            int rollbackDataPolicy) {
        assertInWorkerThread();
        // TODO(b/147666157): Extract the new version number of apk-in-apex
        // The new version for the apk-in-apex is set to 0 for now. If the package is then further
        // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
@@ -414,7 +468,9 @@ class Rollback {
     * Snapshots user data for the provided package and user ids. Does nothing if this rollback is
     * not in the ENABLING state.
     */
    @WorkerThread
    void snapshotUserData(String packageName, int[] userIds, AppDataRollbackHelper dataHelper) {
        assertInWorkerThread();
        synchronized (mLock) {
            if (!isEnabling()) {
                return;
@@ -439,7 +495,9 @@ class Rollback {
     * pending backup, it is updated with a mapping from {@code userId} to inode of the CE user data
     * snapshot.
     */
    @WorkerThread
    void commitPendingBackupAndRestoreForUser(int userId, AppDataRollbackHelper dataHelper) {
        assertInWorkerThread();
        synchronized (mLock) {
            if (dataHelper.commitPendingBackupAndRestoreForUser(userId, this)) {
                RollbackStore.saveRollback(this);
@@ -452,7 +510,9 @@ class Rollback {
     * current time and saves the rollback. Does nothing if this rollback is already in the
     * DELETED state.
     */
    @WorkerThread
    void makeAvailable() {
        assertInWorkerThread();
        synchronized (mLock) {
            if (isDeleted()) {
                Slog.w(TAG, "Cannot make deleted rollback available.");
@@ -467,8 +527,10 @@ class Rollback {
    /**
     * Commits the rollback.
     */
    @WorkerThread
    void commit(final Context context, List<VersionedPackage> causePackages,
            String callerPackageName, IntentSender statusReceiver) {
        assertInWorkerThread();
        synchronized (mLock) {
            if (!isAvailable()) {
                sendFailure(context, statusReceiver,
@@ -565,8 +627,9 @@ class Rollback {
                    parentSession.addChildSessionId(sessionId);
                }

                final LocalIntentReceiver receiver = new LocalIntentReceiver(
                        (Intent result) -> {
                Consumer<Intent> onResult = result -> {
                    mHandler.post(() -> {
                        assertInWorkerThread();
                        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                PackageInstaller.STATUS_FAILURE);
                        if (status != PackageInstaller.STATUS_SUCCESS) {
@@ -621,9 +684,10 @@ class Rollback {
                                    user,
                                    Manifest.permission.MANAGE_ROLLBACKS);
                        }
                        }
                );
                    });
                };

                final LocalIntentReceiver receiver = new LocalIntentReceiver(onResult);
                mState = ROLLBACK_STATE_COMMITTED;
                info.setCommittedSessionId(parentSessionId);
                mRestoreUserDataInProgress = true;
@@ -643,8 +707,10 @@ class Rollback {
     * @return boolean True if this rollback has a restore in progress and contains the specified
     * package.
     */
    @WorkerThread
    boolean restoreUserDataForPackageIfInProgress(String packageName, int[] userIds, int appId,
            String seInfo, AppDataRollbackHelper dataHelper) {
        assertInWorkerThread();
        synchronized (mLock) {
            if (!isRestoreUserDataInProgress()) {
                return false;
@@ -673,7 +739,9 @@ class Rollback {
    /**
     * Deletes app data snapshots associated with this rollback, and moves to the DELETED state.
     */
    @WorkerThread
    void delete(AppDataRollbackHelper dataHelper) {
        assertInWorkerThread();
        synchronized (mLock) {
            boolean containsApex = false;
            for (PackageRollbackInfo pkgInfo : info.getPackages()) {
@@ -701,7 +769,9 @@ class Rollback {
    /**
     * Returns the id of the post-reboot apk session for a staged install, if any.
     */
    @WorkerThread
    int getApkSessionId() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mApkSessionId;
        }
@@ -710,7 +780,9 @@ class Rollback {
    /**
     * Sets the id of the post-reboot apk session for a staged install.
     */
    @WorkerThread
    void setApkSessionId(int apkSessionId) {
        assertInWorkerThread();
        synchronized (mLock) {
            mApkSessionId = apkSessionId;
            RollbackStore.saveRollback(this);
@@ -721,7 +793,9 @@ class Rollback {
     * Returns true if we are expecting the package manager to call restoreUserData for this
     * rollback because it has just been committed but the rollback has not yet been fully applied.
     */
    @WorkerThread
    boolean isRestoreUserDataInProgress() {
        assertInWorkerThread();
        synchronized (mLock) {
            return mRestoreUserDataInProgress;
        }
@@ -731,7 +805,9 @@ class Rollback {
     * Sets whether we are expecting the package manager to call restoreUserData for this
     * rollback because it has just been committed but the rollback has not yet been fully applied.
     */
    @WorkerThread
    void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
        assertInWorkerThread();
        synchronized (mLock) {
            mRestoreUserDataInProgress = restoreUserDataInProgress;
            RollbackStore.saveRollback(this);
@@ -741,7 +817,9 @@ class Rollback {
    /**
     * Returns true if this rollback includes the package with the provided {@code packageName}.
     */
    @WorkerThread
    boolean includesPackage(String packageName) {
        assertInWorkerThread();
        synchronized (mLock) {
            for (PackageRollbackInfo packageRollbackInfo : info.getPackages()) {
                if (packageRollbackInfo.getPackageName().equals(packageName)) {
@@ -756,7 +834,9 @@ class Rollback {
     * Returns true if this rollback includes the package with the provided {@code packageName}
     * with a <i>version rolled back from</i> that is not {@code versionCode}.
     */
    @WorkerThread
    boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
        assertInWorkerThread();
        synchronized (mLock) {
            for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
                if (pkgRollbackInfo.getPackageName().equals(packageName)
@@ -772,7 +852,9 @@ class Rollback {
    /**
     * Returns a list containing the names of all the packages included in this rollback.
     */
    @WorkerThread
    List<String> getPackageNames() {
        assertInWorkerThread();
        synchronized (mLock) {
            List<String> result = new ArrayList<>();
            for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
@@ -785,7 +867,9 @@ class Rollback {
    /**
     * Returns a list containing the names of all the apex packages included in this rollback.
     */
    @WorkerThread
    List<String> getApexPackageNames() {
        assertInWorkerThread();
        synchronized (mLock) {
            List<String> result = new ArrayList<>();
            for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
@@ -800,6 +884,7 @@ class Rollback {
    /**
     * Returns true if this rollback contains the provided {@code packageSessionId}.
     */
    @AnyThread
    boolean containsSessionId(int packageSessionId) {
        for (int id : mPackageSessionIds) {
            if (id == packageSessionId) {
@@ -814,7 +899,9 @@ class Rollback {
     * Returns true when all child sessions are notified with success. This rollback will be
     * enabled only after all child sessions finished with success.
     */
    @WorkerThread
    boolean notifySessionWithSuccess() {
        assertInWorkerThread();
        synchronized (mLock) {
            return ++mNumPackageSessionsWithSuccess == mPackageSessionIds.length;
        }
@@ -825,7 +912,9 @@ class Rollback {
     * until all packages are enabled. Note we don't count apk-in-apex here since they are enabled
     * automatically when the embedding apex is enabled.
     */
    @WorkerThread
    boolean allPackagesEnabled() {
        assertInWorkerThread();
        synchronized (mLock) {
            int packagesWithoutApkInApex = 0;
            for (PackageRollbackInfo rollbackInfo : info.getPackages()) {
@@ -837,6 +926,7 @@ class Rollback {
        }
    }

    @AnyThread
    static String rollbackStateToString(@RollbackState int state) {
        switch (state) {
            case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
@@ -846,6 +936,7 @@ class Rollback {
        throw new AssertionError("Invalid rollback state: " + state);
    }

    @AnyThread
    static @RollbackState int rollbackStateFromString(String state)
            throws ParseException {
        switch (state) {
@@ -856,7 +947,9 @@ class Rollback {
        throw new ParseException("Invalid rollback state: " + state, 0);
    }

    @WorkerThread
    String getStateAsString() {
        assertInWorkerThread();
        synchronized (mLock) {
            return rollbackStateToString(mState);
        }
@@ -893,6 +986,7 @@ class Rollback {
     * Returns true if for any SDK version, the extension version recorded at the time of rollback
     * creation is lower than the current extension version.
     */
    @AnyThread
    private boolean wasCreatedAtLowerExtensionVersion() {
        for (int i = 0; i < mExtensionVersions.size(); i++) {
            if (SdkExtensions.getExtensionVersion(mExtensionVersions.keyAt(i))
@@ -903,6 +997,7 @@ class Rollback {
        return false;
    }

    @AnyThread
    private boolean containsApex() {
        for (PackageRollbackInfo pkgInfo : info.getPackages()) {
            if (pkgInfo.isApex()) {
@@ -912,7 +1007,9 @@ class Rollback {
        return false;
    }

    @WorkerThread
    void dump(IndentingPrintWriter ipw) {
        assertInWorkerThread();
        synchronized (mLock) {
            ipw.println(info.getRollbackId() + ":");
            ipw.increaseIndent();