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

Commit 97fc6d9c authored by Nikita Ioffe's avatar Nikita Ioffe
Browse files

Populate active apexes cache in a background thread

* Split populateActivePackagesCacheIfNeeded into populateApexFilesCache
  and parseApexFiles.
* populateApexFilesCache does an IPC to apexd , while parseApexFiles
  does the heavy lifting of parsing apex files and extracting signature;
* Split is required because during PackageManagerService boot-sequence
  we need to know list of apex packages, and in order to get that
  information we don't need to parse apex files.
* Both populateApexFilesCache and parseApexFiles are enquened to run in
  ApexManagers own HandlerThread so that they don't block other tasks in
  system servers boot sequence.
* Changed ApexManager to use CountDownLatches instead of locks to
  synchronize between thread, as they are more modern and easier to use.

Also did some perf testing on blueline by running
atest google/perf/boottime/boottime-test:

Without https://googleplex-android-review.git.corp.google.com/q/Ic7e5e14ed2d02d3685fd39bb70bc9423ae78f18e:
SystemServerTiming_StartPackageManagerService_avg: 2767.2

With what is currently in qt-dev:
SystemServerTiming_StartPackageManagerService_avg: 3728.4444444444443

Without splitting into populateApexFilesCache and parseApexFiles:
SystemServerTiming_StartPackageManagerService_avg: 3247.5

This change:
SystemServerTiming_StartPackageManagerService_avg: 2894.7

Test: device boots
Test: atest CtsStagedInstallHostTestCases
Bug: 131611765
Change-Id: I980700cd785c22d7f1ace294bb5456056d68baaa
parent ca9056eb
Loading
Loading
Loading
Loading
+109 −55
Original line number Diff line number Diff line
@@ -22,22 +22,21 @@ import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
import android.sysprop.ApexProperties;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;

import java.io.File;
import java.io.PrintWriter;
@@ -46,75 +45,108 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ApexManager class handles communications with the apex service to perform operation and queries,
 * as well as providing caching to avoid unnecessary calls to the service.
 *
 * @hide
 */
class ApexManager {
    static final String TAG = "ApexManager";
    private final IApexService mApexService;
    private final Context mContext;
    private final Object mLock = new Object();
    @GuardedBy("mLock")
public final class ApexManager extends SystemService {
    private static final String TAG = "ApexManager";
    private IApexService mApexService;

    private final CountDownLatch mActivePackagesCacheLatch = new CountDownLatch(1);
    private Map<String, PackageInfo> mActivePackagesCache;

    ApexManager(Context context) {
    private final CountDownLatch mApexFilesCacheLatch = new CountDownLatch(1);
    private ApexInfo[] mApexFiles;

    public ApexManager(Context context) {
        super(context);
    }

    @Override
    public void onStart() {
        try {
            mApexService = IApexService.Stub.asInterface(
                    ServiceManager.getServiceOrThrow("apexservice"));
        } catch (ServiceNotFoundException e) {
            throw new IllegalStateException("Required service apexservice not available");
        }
        mContext = context;
        publishLocalService(ApexManager.class, this);
        HandlerThread oneShotThread = new HandlerThread("ApexManagerOneShotHandler");
        oneShotThread.start();
        oneShotThread.getThreadHandler().post(this::initSequence);
        oneShotThread.quitSafely();
    }

    void systemReady() {
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                onBootCompleted();
                mContext.unregisterReceiver(this);
            }
        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
    private void initSequence() {
        populateApexFilesCache();
        parseApexFiles();
    }

    private void populateActivePackagesCacheIfNeeded() {
        synchronized (mLock) {
            if (mActivePackagesCache != null) {
    private void populateApexFilesCache() {
        if (mApexFiles != null) {
            return;
        }
        long startTimeMicros = SystemClock.currentTimeMicro();
        Slog.i(TAG, "Starting to populate apex files cache");
        try {
            mApexFiles = mApexService.getActivePackages();
            Slog.i(TAG, "IPC to apexd finished in " + (SystemClock.currentTimeMicro()
                    - startTimeMicros) + " μs");
        } catch (RemoteException re) {
            // TODO: make sure this error is propagated to system server.
            Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
            re.rethrowAsRuntimeException();
        }
        mApexFilesCacheLatch.countDown();
        Slog.i(TAG, "Finished populating apex files cache in " + (SystemClock.currentTimeMicro()
                - startTimeMicros) + " μs");
    }

    private void parseApexFiles() {
        waitForLatch(mApexFilesCacheLatch);
        if (mApexFiles == null) {
            throw new IllegalStateException("mApexFiles must be populated");
        }
        long startTimeMicros = SystemClock.currentTimeMicro();
        Slog.i(TAG, "Starting to parse apex files");
        List<PackageInfo> list = new ArrayList<>();
                final ApexInfo[] activePkgs = mApexService.getActivePackages();
                for (ApexInfo ai : activePkgs) {
        // TODO: this can be parallelized.
        for (ApexInfo ai : mApexFiles) {
            try {
                // If the device is using flattened APEX, don't report any APEX
                // packages since they won't be managed or updated by PackageManager.
                if ((new File(ai.packagePath)).isDirectory()) {
                    break;
                }
                    try {
                list.add(PackageParser.generatePackageInfoFromApex(
                        new File(ai.packagePath), PackageManager.GET_META_DATA
                                | PackageManager.GET_SIGNING_CERTIFICATES));
            } catch (PackageParserException pe) {
                // TODO: make sure this error is propagated to system server.
                throw new IllegalStateException("Unable to parse: " + ai, pe);
            }
        }
        mActivePackagesCache = list.stream().collect(
                Collectors.toMap(p -> p.packageName, Function.identity()));
            } catch (RemoteException re) {
                Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
                throw new RuntimeException(re);
            }
        }
        mActivePackagesCacheLatch.countDown();
        Slog.i(TAG, "Finished parsing apex files in " + (SystemClock.currentTimeMicro()
                - startTimeMicros) + " μs");
    }

    /**
     * Retrieves information about an active APEX package.
     *
     * <p>This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in
     * case {@link #parseApexFiles()}} throws an exception this method will never finish
     * essentially putting device into a boot loop.
     *
     * @param packageName the package name to look for. Note that this is the package name reported
     *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
     *                    differ from the one reported in the APEX manifest (i.e.
@@ -123,30 +155,43 @@ class ApexManager {
     *         is not found.
     */
    @Nullable PackageInfo getActivePackage(String packageName) {
        populateActivePackagesCacheIfNeeded();
        waitForLatch(mActivePackagesCacheLatch);
        return mActivePackagesCache.get(packageName);
    }

    /**
     * Retrieves information about all active APEX packages.
     *
     * <p>This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in
     * case {@link #parseApexFiles()}} throws an exception this method will never finish
     * essentially putting device into a boot loop.
     *
     * @return a Collection of PackageInfo object, each one containing information about a different
     *         active package.
     */
    Collection<PackageInfo> getActivePackages() {
        populateActivePackagesCacheIfNeeded();
        waitForLatch(mActivePackagesCacheLatch);
        return mActivePackagesCache.values();
    }

    /**
     * Checks if {@code packageName} is an apex package.
     *
     * <p>This method blocks caller thread until {@link #populateApexFilesCache()} succeeds. Note
     * that in case {@link #populateApexFilesCache()} throws an exception this method will never
     * finish essentially putting device into a boot loop.
     *
     * @param packageName package to check.
     * @return {@code true} if {@code packageName} is an apex package.
     */
    boolean isApexPackage(String packageName) {
        populateActivePackagesCacheIfNeeded();
        return mActivePackagesCache.containsKey(packageName);
        waitForLatch(mApexFilesCacheLatch);
        for (ApexInfo ai : mApexFiles) {
            if (ai.packageName.equals(packageName)) {
                return true;
            }
        }
        return false;
    }

    /**
@@ -273,6 +318,19 @@ class ApexManager {
        }
    }

    /**
     * Blocks current thread until {@code latch} has counted down to zero.
     *
     * @throws RuntimeException if thread was interrupted while waiting.
     */
    private void waitForLatch(CountDownLatch latch) {
        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted waiting for cache to be populated", e);
        }
    }

    /**
     * Dumps various state information to the provided {@link PrintWriter} object.
     *
@@ -286,7 +344,7 @@ class ApexManager {
        ipw.println("Active APEX packages:");
        ipw.increaseIndent();
        try {
            populateActivePackagesCacheIfNeeded();
            waitForLatch(mActivePackagesCacheLatch);
            for (PackageInfo pi : mActivePackagesCache.values()) {
                if (packageName != null && !packageName.equals(pi.packageName)) {
                    continue;
@@ -331,8 +389,4 @@ class ApexManager {
            ipw.println("Couldn't communicate with apexd.");
        }
    }

    public void onBootCompleted() {
        populateActivePackagesCacheIfNeeded();
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -2374,6 +2374,8 @@ public class PackageManagerService extends IPackageManager.Stub
    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        mApexManager = LocalServices.getService(ApexManager.class);
        LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -2470,7 +2472,6 @@ public class PackageManagerService extends IPackageManager.Stub
        mProtectedPackages = new ProtectedPackages(mContext);
        mApexManager = new ApexManager(context);
        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
@@ -21462,7 +21463,6 @@ public class PackageManagerService extends IPackageManager.Stub
        storage.registerListener(mStorageListener);
        mInstallerService.systemReady();
        mApexManager.systemReady();
        mPackageDexOptimizer.systemReady();
        getStorageManagerInternal().addExternalStoragePolicy(
+7 −0
Original line number Diff line number Diff line
@@ -115,6 +115,7 @@ import com.android.server.om.OverlayManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -627,6 +628,12 @@ public final class SystemServer {
        watchdog.start();
        traceEnd();

        // Start ApexManager as early as we can to give it enough time to call apexd and populate
        // cache of known apex packages. Note that calling apexd will happen asynchronously.
        traceBeginAndSlog("StartApexManager");
        mSystemServiceManager.startService(ApexManager.class);
        traceEnd();

        Slog.i(TAG, "Reading configuration...");
        final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
        traceBeginAndSlog(TAG_SYSTEM_CONFIG);