From 98bf12f99989ba2550fac83ee48ecbb6f1582f07 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 26 May 2016 11:56:57 -0600 Subject: [PATCH] Release AssetManagers when ejecting storage. When ejecting a storage device, the system process needs to rapidly release any open FDs to prevent itself from being killed by vold. This change examines all ResourceImpls cached inside the system process and evicts any that reference the storage device being ejected. (ResourcesManager will gladly recreate any evicted entries when asked again in the future.) Also replace broken use of WeakHashMap, since we want the values to be weak references, not the keys. Bug: 28867548 Change-Id: Ib9cfc66497149b6d3f8d49213e9779408a331d2a --- core/java/android/app/ResourcesManager.java | 28 +++++++++- .../android/content/res/ResourcesKey.java | 20 +++++++ .../com/android/server/AttributeCache.java | 56 +++++++++++++------ .../server/pm/PackageManagerService.java | 16 ++++++ 4 files changed, 101 insertions(+), 19 deletions(-) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 25a8b66a42aad..31d254dc5b2bf 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -29,7 +29,6 @@ import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; -import android.os.LocaleList; import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -38,13 +37,12 @@ import android.util.Pair; import android.util.Slog; import android.view.Display; import android.view.DisplayAdjustments; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -120,6 +118,30 @@ public class ResourcesManager { } } + /** + * Invalidate and destroy any resources that reference content under the + * given filesystem path. Typically used when unmounting a storage device to + * try as hard as possible to release any open FDs. + */ + public void invalidatePath(String path) { + synchronized (this) { + int count = 0; + for (int i = 0; i < mResourceImpls.size();) { + final ResourcesKey key = mResourceImpls.keyAt(i); + if (key.isPathReferenced(path)) { + final ResourcesImpl res = mResourceImpls.removeAt(i).get(); + if (res != null) { + res.flushLayoutCache(); + } + count++; + } else { + i++; + } + } + Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path); + } + } + public Configuration getConfiguration() { synchronized (this) { return mResConfiguration; diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java index e89449283c5e2..64b6bf1b6dcd3 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -77,6 +77,26 @@ public final class ResourcesKey { return !Configuration.EMPTY.equals(mOverrideConfiguration); } + public boolean isPathReferenced(String path) { + if (mResDir != null && mResDir.startsWith(path)) { + return true; + } else { + return anyStartsWith(mSplitResDirs, path) || anyStartsWith(mOverlayDirs, path) + || anyStartsWith(mLibDirs, path); + } + } + + private static boolean anyStartsWith(String[] list, String prefix) { + if (list != null) { + for (String s : list) { + if (s != null && s.startsWith(prefix)) { + return true; + } + } + } + return false; + } + @Override public int hashCode() { return mHash; diff --git a/services/core/java/com/android/server/AttributeCache.java b/services/core/java/com/android/server/AttributeCache.java index 427dbc091ced5..57f18c086c56c 100644 --- a/services/core/java/com/android/server/AttributeCache.java +++ b/services/core/java/com/android/server/AttributeCache.java @@ -24,10 +24,12 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.SparseArray; -import java.util.HashMap; -import java.util.WeakHashMap; +import com.android.internal.annotations.GuardedBy; + +import java.lang.ref.WeakReference; /** * TODO: This should be better integrated into the system so it doesn't need @@ -35,17 +37,18 @@ import java.util.WeakHashMap; */ public final class AttributeCache { private static AttributeCache sInstance = null; - + private final Context mContext; - private final WeakHashMap mPackages = - new WeakHashMap(); + + @GuardedBy("this") + private final ArrayMap> mPackages = new ArrayMap<>(); + @GuardedBy("this") private final Configuration mConfiguration = new Configuration(); - + public final static class Package { public final Context context; - private final SparseArray> mMap - = new SparseArray>(); - + private final SparseArray> mMap = new SparseArray<>(); + public Package(Context c) { context = c; } @@ -59,6 +62,12 @@ public final class AttributeCache { context = c; array = ta; } + + void recycle() { + if (array != null) { + array.recycle(); + } + } } public static void init(Context context) { @@ -74,13 +83,27 @@ public final class AttributeCache { public AttributeCache(Context context) { mContext = context; } - + public void removePackage(String packageName) { synchronized (this) { - mPackages.remove(packageName); + final WeakReference ref = mPackages.remove(packageName); + final Package pkg = (ref != null) ? ref.get() : null; + if (pkg != null) { + if (pkg.mMap != null) { + for (int i = 0; i < pkg.mMap.size(); i++) { + final ArrayMap map = pkg.mMap.valueAt(i); + for (int j = 0; j < map.size(); j++) { + map.valueAt(j).recycle(); + } + } + } + + final Resources res = pkg.context.getResources(); + res.flushLayoutCache(); + } } } - + public void updateConfiguration(Configuration config) { synchronized (this) { int changes = mConfiguration.updateFrom(config); @@ -97,8 +120,9 @@ public final class AttributeCache { public Entry get(String packageName, int resId, int[] styleable, int userId) { synchronized (this) { - Package pkg = mPackages.get(packageName); - HashMap map = null; + WeakReference ref = mPackages.get(packageName); + Package pkg = (ref != null) ? ref.get() : null; + ArrayMap map = null; Entry ent = null; if (pkg != null) { map = pkg.mMap.get(resId); @@ -120,11 +144,11 @@ public final class AttributeCache { return null; } pkg = new Package(context); - mPackages.put(packageName, pkg); + mPackages.put(packageName, new WeakReference<>(pkg)); } if (map == null) { - map = new HashMap(); + map = new ArrayMap<>(); pkg.mMap.put(resId, map); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a2bdde41123f6..3450aff49ee93 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -105,6 +105,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityManager; +import android.app.ResourcesManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; import android.app.admin.SecurityLog; @@ -238,6 +239,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.AttributeCache; import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.IntentResolver; @@ -19135,6 +19137,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Slog.w(TAG, "Failed to unload " + ps.codePath); } } + + // Try very hard to release any references to this package + // so we don't risk the system server being killed due to + // open FDs + AttributeCache.instance().removePackage(ps.name); } mSettings.writeLPr(); @@ -19143,6 +19150,15 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded); sendResourcesChangedBroadcast(false, false, unloaded, null); + + // Try very hard to release any references to this path so we don't risk + // the system server being killed due to open FDs + ResourcesManager.getInstance().invalidatePath(vol.getPath().getAbsolutePath()); + + for (int i = 0; i < 3; i++) { + System.gc(); + System.runFinalization(); + } } /**