Merge "More robust createAppData() batching."

This commit is contained in:
Jeff Sharkey
2020-08-15 01:11:48 +00:00
committed by Android (Google) Code Review
3 changed files with 250 additions and 119 deletions

View File

@@ -23,6 +23,8 @@ import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
import android.os.CreateAppDataArgs;
import android.os.CreateAppDataResult;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInstalld;
@@ -39,7 +41,10 @@ import dalvik.system.BlockGuard;
import dalvik.system.VMRuntime;
import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class Installer extends SystemService {
private static final String TAG = "Installer";
@@ -176,37 +181,140 @@ public class Installer extends SystemService {
}
}
private static CreateAppDataArgs buildCreateAppDataArgs(String uuid, String packageName,
int userId, int flags, int appId, String seInfo, int targetSdkVersion) {
final CreateAppDataArgs args = new CreateAppDataArgs();
args.uuid = uuid;
args.packageName = packageName;
args.userId = userId;
args.flags = flags;
args.appId = appId;
args.seInfo = seInfo;
args.targetSdkVersion = targetSdkVersion;
return args;
}
private static CreateAppDataResult buildPlaceholderCreateAppDataResult() {
final CreateAppDataResult result = new CreateAppDataResult();
result.ceDataInode = -1;
result.exceptionCode = 0;
result.exceptionMessage = null;
return result;
}
/**
* @deprecated callers are encouraged to migrate to using {@link Batch} to
* more efficiently handle operations in bulk.
*/
@Deprecated
public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
String seInfo, int targetSdkVersion) throws InstallerException {
if (!checkBeforeRemote()) return -1;
final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
appId, seInfo, targetSdkVersion);
final CreateAppDataResult result = createAppData(args);
if (result.exceptionCode == 0) {
return result.ceDataInode;
} else {
throw new InstallerException(result.exceptionMessage);
}
}
public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args)
throws InstallerException {
if (!checkBeforeRemote()) {
return buildPlaceholderCreateAppDataResult();
}
try {
return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
targetSdkVersion);
return mInstalld.createAppData(args);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public @NonNull CreateAppDataResult[] createAppDataBatched(@NonNull CreateAppDataArgs[] args)
throws InstallerException {
if (!checkBeforeRemote()) {
final CreateAppDataResult[] results = new CreateAppDataResult[args.length];
Arrays.fill(results, buildPlaceholderCreateAppDataResult());
return results;
}
try {
return mInstalld.createAppDataBatched(args);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
/**
* Batched version of createAppData for use with multiple packages.
* Class that collects multiple {@code installd} operations together in an
* attempt to more efficiently execute them in bulk.
* <p>
* Instead of returning results immediately, {@link CompletableFuture}
* instances are returned which can be used to chain follow-up work for each
* request.
* <p>
* The creator of this object <em>must</em> invoke {@link #execute()}
* exactly once to begin execution of all pending operations. Once execution
* has been kicked off, no additional events can be enqueued into this
* instance, but multiple instances can safely exist in parallel.
*/
public void createAppDataBatched(String[] uuids, String[] packageNames, int userId, int flags,
int[] appIds, String[] seInfos, int[] targetSdkVersions) throws InstallerException {
if (!checkBeforeRemote()) return;
final int batchSize = 256;
for (int i = 0; i < uuids.length; i += batchSize) {
int to = i + batchSize;
if (to > uuids.length) {
to = uuids.length;
}
public static class Batch {
private static final int CREATE_APP_DATA_BATCH_SIZE = 256;
try {
mInstalld.createAppDataBatched(Arrays.copyOfRange(uuids, i, to),
Arrays.copyOfRange(packageNames, i, to), userId, flags,
Arrays.copyOfRange(appIds, i, to), Arrays.copyOfRange(seInfos, i, to),
Arrays.copyOfRange(targetSdkVersions, i, to));
} catch (Exception e) {
throw InstallerException.from(e);
private boolean mExecuted;
private final List<CreateAppDataArgs> mArgs = new ArrayList<>();
private final List<CompletableFuture<Long>> mFutures = new ArrayList<>();
/**
* Enqueue the given {@code installd} operation to be executed in the
* future when {@link #execute(Installer)} is invoked.
* <p>
* Callers of this method are not required to hold a monitor lock on an
* {@link Installer} object.
*/
public synchronized @NonNull CompletableFuture<Long> createAppData(String uuid,
String packageName, int userId, int flags, int appId, String seInfo,
int targetSdkVersion) {
if (mExecuted) throw new IllegalStateException();
final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
appId, seInfo, targetSdkVersion);
final CompletableFuture<Long> future = new CompletableFuture<>();
mArgs.add(args);
mFutures.add(future);
return future;
}
/**
* Execute all pending {@code installd} operations that have been
* collected by this batch in a blocking fashion.
* <p>
* Callers of this method <em>must</em> hold a monitor lock on the given
* {@link Installer} object.
*/
public synchronized void execute(@NonNull Installer installer) throws InstallerException {
if (mExecuted) throw new IllegalStateException();
mExecuted = true;
final int size = mArgs.size();
for (int i = 0; i < size; i += CREATE_APP_DATA_BATCH_SIZE) {
final CreateAppDataArgs[] args = new CreateAppDataArgs[Math.min(size - i,
CREATE_APP_DATA_BATCH_SIZE)];
for (int j = 0; j < args.length; j++) {
args[j] = mArgs.get(i + j);
}
final CreateAppDataResult[] results = installer.createAppDataBatched(args);
for (int j = 0; j < args.length; j++) {
final CreateAppDataResult result = results[j];
final CompletableFuture<Long> future = mFutures.get(i + j);
if (result.exceptionCode == 0) {
future.complete(result.ceDataInode);
} else {
future.completeExceptionally(
new InstallerException(result.exceptionMessage));
}
}
}
}
}

View File

@@ -418,6 +418,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -3481,6 +3482,7 @@ public class PackageManagerService extends IPackageManager.Stub
return;
}
int count = 0;
final Installer.Batch batch = new Installer.Batch();
for (String pkgName : deferPackages) {
AndroidPackage pkg = null;
synchronized (mLock) {
@@ -3490,13 +3492,14 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
if (pkg != null) {
synchronized (mInstallLock) {
prepareAppDataAndMigrateLIF(pkg, UserHandle.USER_SYSTEM, storageFlags,
true /* maybeMigrateAppData */);
}
prepareAppDataAndMigrate(batch, pkg, UserHandle.USER_SYSTEM, storageFlags,
true /* maybeMigrateAppData */);
count++;
}
}
synchronized (mInstallLock) {
executeBatchLI(batch);
}
traceLog.traceEnd();
Slog.i(TAG, "Deferred reconcileAppsData finished " + count + " packages");
}, "prepareAppData");
@@ -22743,6 +22746,14 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
private void executeBatchLI(@NonNull Installer.Batch batch) {
try {
batch.execute(mInstaller);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to execute pending operations", e);
}
}
/**
* Examine all apps present on given mounted volume, and destroy apps that
* aren't expected, either due to uninstallation or reinstallation on
@@ -22882,6 +22893,8 @@ public class PackageManagerService extends IPackageManager.Stub
// Ensure that data directories are ready to roll for all packages
// installed for this volume and user
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "prepareAppDataAndMigrate");
Installer.Batch batch = new Installer.Batch();
final List<PackageSetting> packages;
synchronized (mLock) {
packages = mSettings.getVolumePackagesLPr(volumeUuid);
@@ -22902,10 +22915,12 @@ public class PackageManagerService extends IPackageManager.Stub
}
if (ps.getInstalled(userId)) {
prepareAppDataAndMigrateLIF(ps.pkg, userId, flags, migrateAppData);
prepareAppDataAndMigrate(batch, ps.pkg, userId, flags, migrateAppData);
preparedCount++;
}
}
executeBatchLI(batch);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
Slog.v(TAG, "reconcileAppsData finished " + preparedCount + " packages");
return result;
@@ -22930,6 +22945,7 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.writeKernelMappingLPr(ps);
}
Installer.Batch batch = new Installer.Batch();
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
StorageManagerInternal smInternal = mInjector.getStorageManagerInternal();
for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
@@ -22944,16 +22960,20 @@ public class PackageManagerService extends IPackageManager.Stub
if (ps.getInstalled(user.id)) {
// TODO: when user data is locked, mark that we're still dirty
prepareAppDataLIF(pkg, user.id, flags);
if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
// Prepare app data on external storage; currently this is used to
// setup any OBB dirs that were created by the installer correctly.
int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
}
prepareAppData(batch, pkg, user.id, flags).thenRun(() -> {
// Note: this code block is executed with the Installer lock
// already held, since it's invoked as a side-effect of
// executeBatchLI()
if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
// Prepare app data on external storage; currently this is used to
// setup any OBB dirs that were created by the installer correctly.
int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
}
});
}
}
executeBatchLI(batch);
}
/**
@@ -22964,26 +22984,33 @@ public class PackageManagerService extends IPackageManager.Stub
* will try recovering system apps by wiping data; third-party app data is
* left intact.
*/
private void prepareAppDataLIF(AndroidPackage pkg, int userId, int flags) {
private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch,
@Nullable AndroidPackage pkg, int userId, int flags) {
if (pkg == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
return CompletableFuture.completedFuture(null);
}
prepareAppDataLeafLIF(pkg, userId, flags);
return prepareAppDataLeaf(batch, pkg, userId, flags);
}
private void prepareAppDataAndMigrateLIF(AndroidPackage pkg, int userId, int flags,
boolean maybeMigrateAppData) {
prepareAppDataLIF(pkg, userId, flags);
if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
// We may have just shuffled around app data directories, so
// prepare them one more time
prepareAppDataLIF(pkg, userId, flags);
}
private @NonNull CompletableFuture<?> prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
@NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
return prepareAppData(batch, pkg, userId, flags).thenRun(() -> {
// Note: this code block is executed with the Installer lock
// already held, since it's invoked as a side-effect of
// executeBatchLI()
if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
// We may have just shuffled around app data directories, so
// prepare them one more time
final Installer.Batch batchInner = new Installer.Batch();
prepareAppData(batchInner, pkg, userId, flags);
executeBatchLI(batchInner);
}
});
}
private void prepareAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {
private @NonNull CompletableFuture<?> prepareAppDataLeaf(@NonNull Installer.Batch batch,
@NonNull AndroidPackage pkg, int userId, int flags) {
if (DEBUG_APP_DATA) {
Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x"
+ Integer.toHexString(flags));
@@ -23003,57 +23030,67 @@ public class PackageManagerService extends IPackageManager.Stub
Preconditions.checkNotNull(pkgSeInfo);
final String seInfo = pkgSeInfo + (pkg.getSeInfoUser() != null ? pkg.getSeInfoUser() : "");
long ceDataInode = -1;
try {
ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
appId, seInfo, pkg.getTargetSdkVersion());
} catch (InstallerException e) {
if (pkg.isSystem()) {
logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+ ", but trying to recover: " + e);
destroyAppDataLeafLIF(pkg, userId, flags);
try {
ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
appId, seInfo, pkg.getTargetSdkVersion());
logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
} catch (InstallerException e2) {
logCriticalInfo(Log.DEBUG, "Recovery failed!");
}
} else {
Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
}
}
// Prepare the application profiles only for upgrades and first boot (so that we don't
// repeat the same operation at each boot).
// We only have to cover the upgrade and first boot here because for app installs we
// prepare the profiles before invoking dexopt (in installPackageLI).
//
// We also have to cover non system users because we do not call the usual install package
// methods for them.
//
// NOTE: in order to speed up first boot time we only create the current profile and do not
// update the content of the reference profile. A system image should already be configured
// with the right profile keys and the profiles for the speed-profile prebuilds should
// already be copied. That's done in #performDexOptUpgrade.
//
// TODO(calin, mathieuc): We should use .dm files for prebuilds profiles instead of
// manually copying them in #performDexOptUpgrade. When we do that we should have a more
// granular check here and only update the existing profiles.
if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
mArtManagerService.prepareAppProfiles(pkg, userId,
/* updateReferenceProfileContent= */ false);
}
final int targetSdkVersion = pkg.getTargetSdkVersion();
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
// TODO: mark this structure as dirty so we persist it!
synchronized (mLock) {
if (ps != null) {
ps.setCeDataInode(ceDataInode, userId);
}
}
}
return batch.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo,
targetSdkVersion).whenComplete((ceDataInode, e) -> {
// Note: this code block is executed with the Installer lock
// already held, since it's invoked as a side-effect of
// executeBatchLI()
if ((e != null) && pkg.isSystem()) {
logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+ ", but trying to recover: " + e);
destroyAppDataLeafLIF(pkg, userId, flags);
try {
ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId,
flags, appId, seInfo, pkg.getTargetSdkVersion());
logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
} catch (InstallerException e2) {
logCriticalInfo(Log.DEBUG, "Recovery failed!");
}
} else if (e != null) {
Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
}
prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
// Prepare the application profiles only for upgrades and
// first boot (so that we don't repeat the same operation at
// each boot).
//
// We only have to cover the upgrade and first boot here
// because for app installs we prepare the profiles before
// invoking dexopt (in installPackageLI).
//
// We also have to cover non system users because we do not
// call the usual install package methods for them.
//
// NOTE: in order to speed up first boot time we only create
// the current profile and do not update the content of the
// reference profile. A system image should already be
// configured with the right profile keys and the profiles
// for the speed-profile prebuilds should already be copied.
// That's done in #performDexOptUpgrade.
//
// TODO(calin, mathieuc): We should use .dm files for
// prebuilds profiles instead of manually copying them in
// #performDexOptUpgrade. When we do that we should have a
// more granular check here and only update the existing
// profiles.
if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
mArtManagerService.prepareAppProfiles(pkg, userId,
/* updateReferenceProfileContent= */ false);
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
// TODO: mark this structure as dirty so we persist it!
synchronized (mLock) {
if (ps != null) {
ps.setCeDataInode(ceDataInode, userId);
}
}
}
prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
});
}
private void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting,

View File

@@ -102,6 +102,7 @@ import com.android.internal.util.XmlUtils;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.permission.persistence.RuntimePermissionsState;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.Batch;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -4148,24 +4149,12 @@ public final class Settings {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("createNewUser-" + userHandle);
String[] volumeUuids;
String[] names;
int[] appIds;
String[] seinfos;
int[] targetSdkVersions;
int packagesCount;
Installer.Batch batch = new Installer.Batch();
final boolean skipPackageWhitelist = userTypeInstallablePackages == null;
synchronized (mLock) {
Collection<PackageSetting> packages = mPackages.values();
packagesCount = packages.size();
volumeUuids = new String[packagesCount];
names = new String[packagesCount];
appIds = new int[packagesCount];
seinfos = new String[packagesCount];
targetSdkVersions = new int[packagesCount];
Iterator<PackageSetting> packagesIterator = packages.iterator();
for (int i = 0; i < packagesCount; i++) {
PackageSetting ps = packagesIterator.next();
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
final PackageSetting ps = mPackages.valueAt(i);
if (ps.pkg == null) {
continue;
}
@@ -4187,18 +4176,15 @@ public final class Settings {
}
// Need to create a data directory for all apps under this user. Accumulate all
// required args and call the installer after mPackages lock has been released
volumeUuids[i] = ps.volumeUuid;
names[i] = ps.name;
appIds[i] = ps.appId;
seinfos[i] = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
targetSdkVersions[i] = ps.pkg.getTargetSdkVersion();
final String seInfo = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
batch.createAppData(ps.volumeUuid, ps.name, userHandle,
StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE, ps.appId,
seInfo, ps.pkg.getTargetSdkVersion());
}
}
t.traceBegin("createAppData");
final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
installer.createAppDataBatched(volumeUuids, names, userHandle, flags, appIds, seinfos,
targetSdkVersions);
batch.execute(installer);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to prepare app data", e);
}