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

Commit 6c5d9a1a authored by Songchun Fan's avatar Songchun Fan
Browse files

SettingsProvider enhanced cache

Instead of one generation tracker per table, we now use one generation
tracker per setting.

Change overview: https://docs.google.com/document/d/1Hoqnyu-zdqlcqoQPcan2cuV45pXxh_dLtI0kOsOVuX0/edit?resourcekey=0-UoWA1aosEP-04skWe3Ylsw

The testSettingsValueConsecutiveRead now performs 10x better because the
update of one setting doesn't invalidate the caches of other settings.

+ Changed server caching behavior so that nothing is cached until a
  setting is queried.

+ Also moves cache check to before the readable check to further reduce
  read-access time, since if a setting is cached, it must be readable
  for the caller process.

BUG: 228619157
Test: atest com.android.providers.settings.GenerationRegistryTest
Test: atest SettingsProviderTest
Test: atest SettingsProviderPerformanceTest
Test: atest android.provider.NameValueCacheTest
Change-Id: Ie519dadadc13c7211a0f1f5de53f498dfc6edf77
parent 52a61a8b
Loading
Loading
Loading
Loading
+129 −121
Original line number Diff line number Diff line
@@ -116,6 +116,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
 * The Settings provider contains global system-level device preferences.
@@ -2978,19 +2979,22 @@ public final class Settings {
    }
    private static final class GenerationTracker {
        private final MemoryIntArray mArray;
        private final Runnable mErrorHandler;
        @NonNull private final String mName;
        @NonNull private final MemoryIntArray mArray;
        @NonNull private final Consumer<String> mErrorHandler;
        private final int mIndex;
        private int mCurrentGeneration;
        public GenerationTracker(@NonNull MemoryIntArray array, int index,
                int generation, Runnable errorHandler) {
        GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
                int generation, Consumer<String> errorHandler) {
            mName = name;
            mArray = array;
            mIndex = index;
            mErrorHandler = errorHandler;
            mCurrentGeneration = generation;
        }
        // This method also updates the obsolete generation code stored locally
        public boolean isGenerationChanged() {
            final int currentGeneration = readCurrentGeneration();
            if (currentGeneration >= 0) {
@@ -3011,9 +3015,7 @@ public final class Settings {
                return mArray.get(mIndex);
            } catch (IOException e) {
                Log.e(TAG, "Error getting current generation", e);
                if (mErrorHandler != null) {
                    mErrorHandler.run();
                }
                mErrorHandler.accept(mName);
            }
            return -1;
        }
@@ -3023,9 +3025,6 @@ public final class Settings {
                mArray.close();
            } catch (IOException e) {
                Log.e(TAG, "Error closing backing array", e);
                if (mErrorHandler != null) {
                    mErrorHandler.run();
                }
            }
        }
    }
@@ -3088,8 +3087,21 @@ public final class Settings {
        private final ArraySet<String> mAllFields;
        private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
        // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
        @GuardedBy("this")
        private GenerationTracker mGenerationTracker;
        private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
        private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
            synchronized (NameValueCache.this) {
                Log.e(TAG, "Error accessing generation tracker - removing");
                final GenerationTracker tracker = mGenerationTrackers.get(name);
                if (tracker != null) {
                    tracker.destroy();
                    mGenerationTrackers.remove(name);
                }
                mValues.remove(name);
            }
        };
        <T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
                String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
@@ -3178,6 +3190,43 @@ public final class Settings {
        @UnsupportedAppUsage
        public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
            final boolean isSelf = (userHandle == UserHandle.myUserId());
            int currentGeneration = -1;
            boolean needsGenerationTracker = false;
            if (isSelf) {
                synchronized (NameValueCache.this) {
                    final GenerationTracker generationTracker = mGenerationTrackers.get(name);
                    if (generationTracker != null) {
                        if (generationTracker.isGenerationChanged()) {
                            if (DEBUG) {
                                Log.i(TAG, "Generation changed for setting:" + name
                                        + " type:" + mUri.getPath()
                                        + " in package:" + cr.getPackageName()
                                        + " and user:" + userHandle);
                            }
                            mValues.remove(name);
                        } else if (mValues.containsKey(name)) {
                            if (DEBUG) {
                                Log.i(TAG, "Cache hit for setting:" + name);
                            }
                            return mValues.get(name);
                        }
                        currentGeneration = generationTracker.getCurrentGeneration();
                    } else {
                        needsGenerationTracker = true;
                    }
                }
            } else {
                if (LOCAL_LOGV) {
                    Log.v(TAG, "get setting for user " + userHandle
                            + " by user " + UserHandle.myUserId() + " so skipping cache");
                }
            }
            if (DEBUG) {
                Log.i(TAG, "Cache miss for setting:" + name);
            }
            // Check if the target settings key is readable. Reject if the caller is not system and
            // is trying to access a settings key defined in the Settings.Secure, Settings.System or
            // Settings.Global and is not annotated as @Readable.
@@ -3211,31 +3260,6 @@ public final class Settings {
                }
            }
            final boolean isSelf = (userHandle == UserHandle.myUserId());
            int currentGeneration = -1;
            if (isSelf) {
                synchronized (NameValueCache.this) {
                    if (mGenerationTracker != null) {
                        if (mGenerationTracker.isGenerationChanged()) {
                            if (DEBUG) {
                                Log.i(TAG, "Generation changed for type:"
                                        + mUri.getPath() + " in package:"
                                        + cr.getPackageName() +" and user:" + userHandle);
                            }
                            mValues.clear();
                        } else if (mValues.containsKey(name)) {
                            return mValues.get(name);
                        }
                        if (mGenerationTracker != null) {
                            currentGeneration = mGenerationTracker.getCurrentGeneration();
                        }
                    }
                }
            } else {
                if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
                        + " by user " + UserHandle.myUserId() + " so skipping cache");
            }
            IContentProvider cp = mProviderHolder.getProvider(cr);
            // Try the fast path first, not using query().  If this
@@ -3244,24 +3268,17 @@ public final class Settings {
            // interface.
            if (mCallGetCommand != null) {
                try {
                    Bundle args = null;
                    Bundle args = new Bundle();
                    if (!isSelf) {
                        args = new Bundle();
                        args.putInt(CALL_METHOD_USER_KEY, userHandle);
                    }
                    boolean needsGenerationTracker = false;
                    synchronized (NameValueCache.this) {
                        if (isSelf && mGenerationTracker == null) {
                            needsGenerationTracker = true;
                            if (args == null) {
                                args = new Bundle();
                            }
                    if (needsGenerationTracker) {
                        args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
                        if (DEBUG) {
                                Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
                                        + " in package:" + cr.getPackageName() +" and user:"
                                        + userHandle);
                            }
                            Log.i(TAG, "Requested generation tracker for setting:" + name
                                    + " type:" + mUri.getPath()
                                    + " in package:" + cr.getPackageName()
                                    + " and user:" + userHandle);
                        }
                    }
                    Bundle b;
@@ -3298,33 +3315,24 @@ public final class Settings {
                                        final int generation = b.getInt(
                                                CALL_METHOD_GENERATION_KEY, 0);
                                        if (DEBUG) {
                                            Log.i(TAG, "Received generation tracker for type:"
                                                    + mUri.getPath() + " in package:"
                                                    + cr.getPackageName() + " and user:"
                                                    + userHandle + " with index:" + index);
                                        }
                                        if (mGenerationTracker != null) {
                                            mGenerationTracker.destroy();
                                        }
                                        mGenerationTracker = new GenerationTracker(array, index,
                                                generation, () -> {
                                            synchronized (NameValueCache.this) {
                                                Log.e(TAG, "Error accessing generation"
                                                        + " tracker - removing");
                                                if (mGenerationTracker != null) {
                                                    GenerationTracker generationTracker =
                                                            mGenerationTracker;
                                                    mGenerationTracker = null;
                                                    generationTracker.destroy();
                                                    mValues.clear();
                                                }
                                            }
                                        });
                                            Log.i(TAG, "Received generation tracker for setting:"
                                                    + name
                                                    + " type:" + mUri.getPath()
                                                    + " in package:" + cr.getPackageName()
                                                    + " and user:" + userHandle
                                                    + " with index:" + index);
                                        }
                                        mGenerationTrackers.put(name, new GenerationTracker(name,
                                                array, index, generation,
                                                mGenerationTrackerErrorHandler));
                                        currentGeneration = generation;
                                    }
                                }
                                if (mGenerationTracker != null && currentGeneration ==
                                        mGenerationTracker.getCurrentGeneration()) {
                                if (mGenerationTrackers.get(name) != null && currentGeneration
                                        == mGenerationTrackers.get(name).getCurrentGeneration()) {
                                    if (DEBUG) {
                                        Log.i(TAG, "Updating cache for setting:" + name);
                                    }
                                    mValues.put(name, value);
                                }
                            }
@@ -3367,14 +3375,13 @@ public final class Settings {
                String value = c.moveToNext() ? c.getString(0) : null;
                synchronized (NameValueCache.this) {
                    if (mGenerationTracker != null
                            && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
                        mValues.put(name, value);
                    if (mGenerationTrackers.get(name) != null && currentGeneration
                            == mGenerationTrackers.get(name).getCurrentGeneration()) {
                        if (DEBUG) {
                            Log.i(TAG, "Updating cache for setting:" + name + " using query");
                        }
                        mValues.put(name, value);
                    }
                if (LOCAL_LOGV) {
                    Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
                            name + " = " + (value == null ? "(null)" : value));
                }
                return value;
            } catch (RemoteException e) {
@@ -3409,18 +3416,29 @@ public final class Settings {
            Config.enforceReadPermission(namespace);
            ArrayMap<String, String> keyValues = new ArrayMap<>();
            int currentGeneration = -1;
            boolean needsGenerationTracker = false;
            synchronized (NameValueCache.this) {
                if (mGenerationTracker != null) {
                    if (mGenerationTracker.isGenerationChanged()) {
                final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
                if (generationTracker != null) {
                    if (generationTracker.isGenerationChanged()) {
                        if (DEBUG) {
                            Log.i(TAG, "Generation changed for type:" + mUri.getPath()
                            Log.i(TAG, "Generation changed for prefix:" + prefix
                                    + " type:" + mUri.getPath()
                                    + " in package:" + cr.getPackageName());
                        }
                        mValues.clear();
                        for (int i = 0; i < mValues.size(); ++i) {
                            String key = mValues.keyAt(i);
                            if (key.startsWith(prefix)) {
                                mValues.remove(key);
                            }
                        }
                    } else {
                        boolean prefixCached = mValues.containsKey(prefix);
                        if (prefixCached) {
                            if (DEBUG) {
                                Log.i(TAG, "Cache hit for prefix:" + prefix);
                            }
                            if (!names.isEmpty()) {
                                for (String name : names) {
                                    if (mValues.containsKey(name)) {
@@ -3440,9 +3458,9 @@ public final class Settings {
                            return keyValues;
                        }
                    }
                    if (mGenerationTracker != null) {
                        currentGeneration = mGenerationTracker.getCurrentGeneration();
                    }
                    currentGeneration = generationTracker.getCurrentGeneration();
                } else {
                    needsGenerationTracker = true;
                }
            }
@@ -3450,20 +3468,20 @@ public final class Settings {
                // No list command specified, return empty map
                return keyValues;
            }
            if (DEBUG) {
                Log.i(TAG, "Cache miss for prefix:" + prefix);
            }
            IContentProvider cp = mProviderHolder.getProvider(cr);
            try {
                Bundle args = new Bundle();
                args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
                boolean needsGenerationTracker = false;
                synchronized (NameValueCache.this) {
                    if (mGenerationTracker == null) {
                        needsGenerationTracker = true;
                if (needsGenerationTracker) {
                    args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
                    if (DEBUG) {
                            Log.i(TAG, "Requested generation tracker for type: "
                                    + mUri.getPath() + " in package:" + cr.getPackageName());
                        }
                        Log.i(TAG, "Requested generation tracker for prefix:" + prefix
                                + " type: " + mUri.getPath()
                                + " in package:" + cr.getPackageName());
                    }
                }
@@ -3516,32 +3534,22 @@ public final class Settings {
                            final int generation = b.getInt(
                                    CALL_METHOD_GENERATION_KEY, 0);
                            if (DEBUG) {
                                Log.i(TAG, "Received generation tracker for type:"
                                        + mUri.getPath() + " in package:"
                                        + cr.getPackageName() + " with index:" + index);
                            }
                            if (mGenerationTracker != null) {
                                mGenerationTracker.destroy();
                            }
                            mGenerationTracker = new GenerationTracker(array, index,
                                    generation, () -> {
                                synchronized (NameValueCache.this) {
                                    Log.e(TAG, "Error accessing generation tracker"
                                            + " - removing");
                                    if (mGenerationTracker != null) {
                                        GenerationTracker generationTracker =
                                                mGenerationTracker;
                                        mGenerationTracker = null;
                                        generationTracker.destroy();
                                        mValues.clear();
                                    }
                                }
                            });
                                Log.i(TAG, "Received generation tracker for prefix:" + prefix
                                        + " type:" + mUri.getPath()
                                        + " in package:" + cr.getPackageName()
                                        + " with index:" + index);
                            }
                            mGenerationTrackers.put(prefix,
                                    new GenerationTracker(prefix, array, index, generation,
                                            mGenerationTrackerErrorHandler));
                            currentGeneration = generation;
                        }
                    }
                    if (mGenerationTracker != null && currentGeneration
                            == mGenerationTracker.getCurrentGeneration()) {
                    if (mGenerationTrackers.get(prefix) != null && currentGeneration
                            == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
                        if (DEBUG) {
                            Log.i(TAG, "Updating cache for prefix:" + prefix);
                        }
                        // cache the complete list of flags for the namespace
                        mValues.putAll(flagsToValues);
                        // Adding the prefix as a signal that the prefix is cached.
@@ -3557,11 +3565,11 @@ public final class Settings {
        public void clearGenerationTrackerForTest() {
            synchronized (NameValueCache.this) {
                if (mGenerationTracker != null) {
                    mGenerationTracker.destroy();
                for (int i = 0; i < mGenerationTrackers.size(); i++) {
                    mGenerationTrackers.valueAt(i).destroy();
                }
                mGenerationTrackers.clear();
                mValues.clear();
                mGenerationTracker = null;
            }
        }
    }
+200 −59

File changed.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ android_test {
        "test/**/*.java",
        "src/android/provider/settings/backup/*",
        "src/android/provider/settings/validators/*",
        "src/com/android/providers/settings/GenerationRegistry.java",
        "src/com/android/providers/settings/SettingsBackupAgent.java",
        "src/com/android/providers/settings/SettingsState.java",
        "src/com/android/providers/settings/SettingsHelper.java",
+129 −80

File changed.

Preview size limit exceeded, changes collapsed.

+21 −18
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OV
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
import static com.android.providers.settings.SettingsState.makeKey;

import android.Manifest;
import android.annotation.NonNull;
@@ -375,10 +376,6 @@ public class SettingsProvider extends ContentProvider {
    @GuardedBy("mLock")
    private boolean mSyncConfigDisabledUntilReboot;

    public static int makeKey(int type, int userId) {
        return SettingsState.makeKey(type, userId);
    }

    public static int getTypeFromKey(int key) {
        return SettingsState.getTypeFromKey(key);
    }
@@ -387,9 +384,6 @@ public class SettingsProvider extends ContentProvider {
        return SettingsState.getUserIdFromKey(key);
    }

    public static String keyToString(int key) {
        return SettingsState.keyToString(key);
    }
    @ChangeId
    @EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
    private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -553,7 +547,7 @@ public class SettingsProvider extends ContentProvider {

            case Settings.CALL_METHOD_LIST_CONFIG: {
                String prefix = getSettingPrefix(args);
                Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
                Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
                        isTrackingGeneration(args));
                reportDeviceConfigAccess(prefix);
                return result;
@@ -1319,6 +1313,7 @@ public class SettingsProvider extends ContentProvider {
        return false;
    }

    @NonNull
    private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
        if (DEBUG) {
            Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2316,7 +2311,8 @@ public class SettingsProvider extends ContentProvider {
                "get/set setting for user", null);
    }

    private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
    private Bundle packageValueForCallResult(@Nullable Setting setting,
            boolean trackingGeneration) {
        if (!trackingGeneration) {
            if (setting == null || setting.isNull()) {
                return NULL_SETTING_BUNDLE;
@@ -2327,19 +2323,26 @@ public class SettingsProvider extends ContentProvider {
        result.putString(Settings.NameValueTable.VALUE,
                !setting.isNull() ? setting.getValue() : null);

        mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
        if (setting != null && !setting.isNull()) {
            // No need to track generation if the setting doesn't exist
            synchronized (mLock) {
                mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey(),
                        setting.getName());
            }
        }
        return result;
    }

    private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
            boolean trackingGeneration) {
    private Bundle packageValuesForCallResult(String prefix,
            @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
        Bundle result = new Bundle();
        result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
        if (trackingGeneration) {
        if (trackingGeneration && !keyValues.isEmpty()) {
            // No need to track generation if the namespace is empty
            synchronized (mLock) {
                mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
                        mSettingsRegistry.getSettingsLocked(
                                SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
                        mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
                                UserHandle.USER_SYSTEM).mKey, prefix);
            }
        }

@@ -3449,7 +3452,7 @@ public class SettingsProvider extends ContentProvider {

        private void notifyForSettingsChange(int key, String name) {
            // Increment the generation first, so observers always see the new value
            mGenerationRegistry.incrementGeneration(key);
            mGenerationRegistry.incrementGeneration(key, name);

            if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
                final long token = Binder.clearCallingIdentity();
@@ -3487,7 +3490,7 @@ public class SettingsProvider extends ContentProvider {
                List<String> changedSettings) {

            // Increment the generation first, so observers always see the new value
            mGenerationRegistry.incrementGeneration(key);
            mGenerationRegistry.incrementGeneration(key, prefix);

            StringBuilder stringBuilder = new StringBuilder(prefix);
            for (int i = 0; i < changedSettings.size(); ++i) {
@@ -3513,7 +3516,7 @@ public class SettingsProvider extends ContentProvider {
                    if (profileId != userId) {
                        final int key = makeKey(type, profileId);
                        // Increment the generation first, so observers always see the new value
                        mGenerationRegistry.incrementGeneration(key);
                        mGenerationRegistry.incrementGeneration(key, name);
                        mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
                                profileId, 0, uri).sendToTarget();
                    }
Loading