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

Commit c16c6a2a authored by Winson Chiu's avatar Winson Chiu Committed by Android (Google) Code Review
Browse files

Merge changes I78cd6a32,I99656d57 into tm-dev

* changes:
  Fix PMS recordInitialState on retry
  Avoid taking PMS snapshot lock when snapshot is still valid
parents 3ffed0bc 2a5f1edc
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -94,12 +94,13 @@ import java.util.Set;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public interface Computer extends PackageDataSnapshot {

    int getVersion();

    /**
     * Administrative statistics: record that the snapshot has been used.  Every call
     * to use() increments the usage counter.
     */
    default void use() {
    }
    Computer use();
    /**
     * Fetch the snapshot usage counter.
     * @return The number of times this snapshot was used.
+11 −2
Original line number Diff line number Diff line
@@ -367,6 +367,8 @@ public class ComputerEngine implements Computer {
        return (v1 > v2) ? -1 : ((v1 < v2) ? 1 : 0);
    };

    private final int mVersion;

    // The administrative use counter.
    private int mUsed = 0;

@@ -424,7 +426,8 @@ public class ComputerEngine implements Computer {
        return mLocalAndroidApplication;
    }

    ComputerEngine(PackageManagerService.Snapshot args) {
    ComputerEngine(PackageManagerService.Snapshot args, int version) {
        mVersion = version;
        mSettings = new Settings(args.settings);
        mIsolatedOwners = args.isolatedOwners;
        mPackages = args.packages;
@@ -464,11 +467,17 @@ public class ComputerEngine implements Computer {
        mService = args.service;
    }

    @Override
    public int getVersion() {
        return mVersion;
    }

    /**
     * Record that the snapshot was used.
     */
    public final void use() {
    public final Computer use() {
        mUsed++;
        return this;
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import com.android.internal.annotations.VisibleForTesting;
public final class ComputerLocked extends ComputerEngine {

    ComputerLocked(PackageManagerService.Snapshot args) {
        super(args);
        super(args, -1);
    }

    protected ComponentName resolveComponentName() {
+63 −58
Original line number Diff line number Diff line
@@ -271,8 +271,8 @@ import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
@@ -1038,22 +1038,15 @@ public class PackageManagerService implements PackageSender, TestUtilityService
    // times during the PackageManagerService constructor but it should not be modified thereafter.
    private ComputerLocked mLiveComputer;

    // A lock-free cache for frequently called functions.
    private volatile Computer mSnapshotComputer;
    private static final AtomicReference<Computer> sSnapshot = new AtomicReference<>();

    // If true, the snapshot is invalid (stale).  The attribute is static since it may be
    // set from outside classes.  The attribute may be set to true anywhere, although it
    // should only be set true while holding mLock.  However, the attribute id guaranteed
    // to be set false only while mLock and mSnapshotLock are both held.
    private static final AtomicBoolean sSnapshotInvalid = new AtomicBoolean(true);

    static final ThreadLocal<ThreadComputer> sThreadComputer =
            ThreadLocal.withInitial(ThreadComputer::new);
    // If this differs from Computer#getVersion, the snapshot is invalid (stale).
    private static final AtomicInteger sSnapshotPendingVersion = new AtomicInteger(1);

    /**
     * This lock is used to make reads from {@link #sSnapshotInvalid} and
     * {@link #mSnapshotComputer} atomic inside {@code snapshotComputer()}.  This lock is
     * not meant to be used outside that method.  This lock must be taken before
     * This lock is used to make reads from {@link #sSnapshotPendingVersion} and
     * {@link #sSnapshot} atomic inside {@code snapshotComputer()} when the versions mismatch.
     * This lock is not meant to be used outside that method. This lock must be taken before
     * {@link #mLock} is taken.
     */
    private final Object mSnapshotLock = new Object();
@@ -1077,48 +1070,53 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            // yet invalidated the snapshot.  Always give the thread the live computer.
            return mLiveComputer;
        }

        var oldSnapshot = sSnapshot.get();
        var pendingVersion = sSnapshotPendingVersion.get();

        if (oldSnapshot != null && oldSnapshot.getVersion() == pendingVersion) {
            return oldSnapshot.use();
        }

        synchronized (mSnapshotLock) {
            // This synchronization block serializes access to the snapshot computer and
            // to the code that samples mSnapshotInvalid.
            Computer c = mSnapshotComputer;
            if (sSnapshotInvalid.getAndSet(false) || (c == null)) {
                // The snapshot is invalid if it is marked as invalid or if it is null.  If it
                // is null, then it is currently being rebuilt by rebuildSnapshot().
                synchronized (mLock) {
                    // Rebuild the snapshot if it is invalid.  Note that the snapshot might be
                    // invalidated as it is rebuilt.  However, the snapshot is still
                    // self-consistent (the lock is being held) and is current as of the time
                    // this function is entered.
                    rebuildSnapshot();
            // Re-capture pending version in case a new invalidation occurred since last check
            var rebuildSnapshot = sSnapshot.get();
            var rebuildVersion = sSnapshotPendingVersion.get();

                    // Guaranteed to be non-null.  mSnapshotComputer is only be set to null
                    // temporarily in rebuildSnapshot(), which is guarded by mLock().  Since
                    // the mLock is held in this block and since rebuildSnapshot() is
                    // complete, the attribute can not now be null.
                    c = mSnapshotComputer;
            // Check the versions again while the lock is held, in case the rebuild time caused
            // multiple threads to wait on the snapshot lock. When the first thread finishes
            // a rebuild, the snapshot is now valid and the other waiting threads can use it
            // without kicking off their own rebuilds.
            if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
                return rebuildSnapshot.use();
            }

            synchronized (mLock) {
                // Fetch version one last time to ensure that the rebuilt snapshot matches
                // the latest invalidation, which could have come in between entering the
                // SnapshotLock and mLock sync blocks.
                rebuildVersion = sSnapshotPendingVersion.get();

                // Build the snapshot for this version
                var newSnapshot = rebuildSnapshot(rebuildSnapshot, rebuildVersion);
                sSnapshot.set(newSnapshot);
                return newSnapshot.use();
            }
            c.use();
            return c;
        }
    }

    /**
     * Rebuild the cached computer.  mSnapshotComputer is temporarily set to null to block other
     * threads from using the invalid computer until it is rebuilt.
     */
    @GuardedBy({ "mLock", "mSnapshotLock"})
    private void rebuildSnapshot() {
        final long now = SystemClock.currentTimeMicro();
        final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed();
        mSnapshotComputer = null;
        final Snapshot args = new Snapshot(Snapshot.SNAPPED);
        mSnapshotComputer = new ComputerEngine(args);
        final long done = SystemClock.currentTimeMicro();
    private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
        var now = SystemClock.currentTimeMicro();
        var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
        var args = new Snapshot(Snapshot.SNAPPED);
        var newSnapshot = new ComputerEngine(args, newVersion);
        var done = SystemClock.currentTimeMicro();

        if (mSnapshotStatistics != null) {
            mSnapshotStatistics.rebuild(now, done, hits);
        }
        return newSnapshot;
    }

    /**
@@ -1138,7 +1136,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
        if (TRACE_SNAPSHOTS) {
            Log.i(TAG, "snapshot: onChange(" + what + ")");
        }
        sSnapshotInvalid.set(true);
        sSnapshotPendingVersion.incrementAndGet();
    }

    /**
@@ -1665,7 +1663,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
        mRequiredSdkSandboxPackage = testParams.requiredSdkSandboxPackage;

        mLiveComputer = createLiveComputer();
        mSnapshotComputer = null;
        mSnapshotStatistics = null;

        mPackages.putAll(testParams.packages);
@@ -1855,9 +1852,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            // cached computer is the same as the live computer until the end of the
            // constructor, at which time the invalidation method updates it.
            mSnapshotStatistics = new SnapshotStatistics();
            sSnapshotInvalid.set(true);
            sSnapshotPendingVersion.incrementAndGet();
            mLiveComputer = createLiveComputer();
            mSnapshotComputer = null;
            registerObservers(true);
        }

@@ -5388,10 +5384,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
        @Override
        public void setApplicationCategoryHint(String packageName, int categoryHint,
                String callerPackageName) {
            final PackageStateMutator.InitialState initialState = recordInitialState();

            final FunctionalUtils.ThrowingFunction<Computer, PackageStateMutator.Result>
                    implementation = computer -> {
            final FunctionalUtils.ThrowingBiFunction<PackageStateMutator.InitialState, Computer,
                    PackageStateMutator.Result> implementation = (initialState, computer) -> {
                if (computer.getInstantAppPackageName(Binder.getCallingUid()) != null) {
                    throw new SecurityException(
                            "Instant applications don't have access to this method");
@@ -5419,12 +5413,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService
                }
            };

            PackageStateMutator.Result result = implementation.apply(snapshotComputer());
            PackageStateMutator.Result result =
                    implementation.apply(recordInitialState(), snapshotComputer());
            if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) {
                // TODO: Specific return value of what state changed?
                // The installer on record might have changed, retry with lock
                synchronized (mPackageStateWriteLock) {
                    result = implementation.apply(snapshotComputer());
                    result = implementation.apply(recordInitialState(), snapshotComputer());
                }
            }

@@ -7156,9 +7151,19 @@ public class PackageManagerService implements PackageSender, TestUtilityService
    public PackageStateMutator.Result commitPackageStateMutation(
            @Nullable PackageStateMutator.InitialState initialState, @NonNull String packageName,
            @NonNull Consumer<PackageStateWrite> consumer) {
        PackageStateMutator.Result result = null;
        if (Thread.holdsLock(mPackageStateWriteLock)) {
            // If the thread is already holding the lock, this is likely a retry based on a prior
            // failure, and re-calculating whether a state change occurred can be skipped.
            result = PackageStateMutator.Result.SUCCESS;
        }
        synchronized (mPackageStateWriteLock) {
            final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
            if (result == null) {
                // If the thread wasn't previously holding, this is a first-try commit and so a
                // state change may have happened.
                result = mPackageStateMutator.generateResult(
                        initialState, mChangedPackagesTracker.getSequenceNumber());
            }
            if (result != PackageStateMutator.Result.SUCCESS) {
                return result;
            }
+0 −52
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.pm;

/**
 * This class records the Computer being used by a thread and the Computer's reference
 * count.  There is a thread-local copy of this class.
 */
public final class ThreadComputer implements AutoCloseable {
    Computer mComputer = null;
    int mRefCount = 0;

    void acquire(Computer c) {
        if (mRefCount != 0 && mComputer != c) {
            throw new RuntimeException("computer mismatch, count = " + mRefCount);
        }
        mComputer = c;
        mRefCount++;
    }

    void acquire() {
        if (mRefCount == 0 || mComputer == null) {
            throw new RuntimeException("computer acquire on empty ref count");
        }
        mRefCount++;
    }

    void release() {
        if (--mRefCount == 0) {
            mComputer = null;
        }
    }

    @Override
    public void close() {
        release();
    }
}