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

Commit acae3c0f authored by JW Wang's avatar JW Wang
Browse files

Ensure mutation to multiple objects is done atomically

Acquire both parent and child locks before mutation begins.
This ensures no concurrent mutation is possible on another thread.

Bug: 161954235
Test: atest StagedInstallTest AtomicInstallTest
Change-Id: If4b83ef83bbe7e85e91a6b8f16e01d4dc0d6d8e6
parent be703849
Loading
Loading
Loading
Loading
+59 −17
Original line number Diff line number Diff line
@@ -157,6 +157,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@@ -262,6 +263,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

    private final Object mLock = new Object();

    /**
     * Used to detect and reject concurrent access to this session object to ensure mutation
     * to multiple objects like {@link #addChildSessionId} are done atomically.
     */
    private final AtomicBoolean mTransactionLock = new AtomicBoolean(false);

    /** Timestamp of the last time this session changed state  */
    @GuardedBy("mLock")
    private long updatedMillis;
@@ -3073,12 +3080,31 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    private void acquireTransactionLock() {
        if (!mTransactionLock.compareAndSet(false, true)) {
            throw new UnsupportedOperationException("Concurrent access not supported");
        }
    }

    private void releaseTransactionLock() {
        mTransactionLock.compareAndSet(true, false);
    }

    @Override
    public void addChildSessionId(int childSessionId) {
        final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
        if (childSession == null || !childSession.canBeAddedAsChild(sessionId)) {
        if (childSession == null) {
            throw new IllegalStateException("Unable to add child session " + childSessionId
                    + " as it does not exist.");
        }

        try {
            acquireTransactionLock();
            childSession.acquireTransactionLock();

            if (!childSession.canBeAddedAsChild(sessionId)) {
                throw new IllegalStateException("Unable to add child session " + childSessionId
                            + " as it does not exist or is in an invalid state.");
                        + " as it is in an invalid state.");
            }
            synchronized (mLock) {
                assertCallerIsOwnerOrRootLocked();
@@ -3091,11 +3117,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                childSession.setParentSessionId(this.sessionId);
                addChildSessionIdLocked(childSessionId);
            }
        } finally {
            releaseTransactionLock();
            childSession.releaseTransactionLock();
        }
    }

    @Override
    public void removeChildSessionId(int sessionId) {
        final PackageInstallerSession session = mSessionProvider.getSession(sessionId);
        try {
            acquireTransactionLock();
            if (session != null) {
                session.acquireTransactionLock();
            }

            synchronized (mLock) {
                final int indexOfSession = mChildSessionIds.indexOfKey(sessionId);
                if (session != null) {
@@ -3107,6 +3143,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                }
                mChildSessionIds.removeAt(indexOfSession);
            }
        } finally {
            releaseTransactionLock();
            if (session != null) {
                session.releaseTransactionLock();
            }
        }
    }

    /**