/* * Copyright (C) 2022 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 android.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.PropertyInvalidatedCache; import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastPrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicLong; /** * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing, * but doesn't hold a lock across data fetches on query misses. * * The intended use case is caching frequently-read, seldom-changed information normally retrieved * across interprocess communication. Imagine that you've written a user birthday information * daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface over * binder. That binder interface looks something like this: * *
* parcelable Birthday { * int month; * int day; * } * interface IUserBirthdayService { * Birthday getUserBirthday(int userId); * } ** * Suppose the service implementation itself looks like this... * *
* public class UserBirthdayServiceImpl implements IUserBirthdayService { * private final HashMap<Integer, Birthday%> mUidToBirthday; * {@literal @}Override * public synchronized Birthday getUserBirthday(int userId) { * return mUidToBirthday.get(userId); * } * private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) { * mUidToBirthday.clear(); * mUidToBirthday.putAll(uidToBirthday); * } * } ** * ... and we have a client in frameworks (loaded into every app process) that looks like this: * *
* public class ActivityThread { * ... * public Birthday getUserBirthday(int userId) { * return GetService("birthdayd").getUserBirthday(userId); * } * ... * } ** * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call to * the birthdayd process and consult its database of birthdays. If we query user birthdays * frequently, we do a lot of work that we don't have to do, since user birthdays change * infrequently. * * IpcDataCache is part of a pattern for optimizing this kind of information-querying code. Using * {@code IpcDataCache}, you'd write the client this way: * *
* public class ActivityThread { * ... * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery = * new IpcDataCache.QueryHandler<Integer, Birthday>() { * {@literal @}Override * public Birthday apply(Integer) { * return GetService("birthdayd").getUserBirthday(userId); * } * }; * private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache * private static final String BDAY_API = "getUserBirthday"; * private final IpcDataCache<Integer, Birthday%> mBirthdayCache = new * IpcDataCache<Integer, Birthday%>( * BDAY_CACHE_MAX, MODULE_SYSTEM, BDAY_API, BDAY_API, mBirthdayQuery); * * public void disableUserBirthdayCache() { * mBirthdayCache.disableForCurrentProcess(); * } * public void invalidateUserBirthdayCache() { * mBirthdayCache.invalidateCache(); * } * public Birthday getUserBirthday(int userId) { * return mBirthdayCache.query(userId); * } * ... * } ** * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday * for the first time; on subsequent queries, we return the already-known Birthday object. * * The second parameter to the IpcDataCache constructor is a string that identifies the "module" * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any * string is permitted. The third parameters is the name of the API being cached; this, too, can * any value. The fourth is the name of the cache. The cache is usually named after th API. * Some things you must know about the three strings: *
* public class UserBirthdayServiceImpl { * ... * public UserBirthdayServiceImpl() { * ... * ActivityThread.currentActivityThread().disableUserBirthdayCache(); * ActivityThread.currentActivityThread().invalidateUserBirthdayCache(); * } * * private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) { * mUidToBirthday.clear(); * mUidToBirthday.putAll(uidToBirthday); * ActivityThread.currentActivityThread().invalidateUserBirthdayCache(); * } * ... * } ** * The call to {@code IpcDataCache.invalidateCache()} guarantees that all clients will re-fetch * birthdays from binder during consequent calls to * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock * held, we maintain consistency between different client views of the birthday state. The use of * IpcDataCache in this idiomatic way introduces no new race conditions. * * IpcDataCache has a few other features for doing things like incremental enhancement of cached * values and invalidation of multiple caches (that all share the same property key) at once. * * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each * time we update the cache. SELinux configuration must allow everyone to read this property * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write * the property. (These properties conventionally begin with the "cache_key." prefix.) * * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In this * local case, there's no IPC, so use of the cache is (depending on exact circumstance) * unnecessary. * * There may be queries for which it is more efficient to bypass the cache than to cache the * result. This would be true, for example, if some queries would require frequent cache * invalidation while other queries require infrequent invalidation. To expand on the birthday * example, suppose that there is a userId that signifies "the next birthday". When passed this * userId, the server returns the next birthday among all users - this value changes as time * advances. The userId value can be cached, but the cache must be invalidated whenever a * birthday occurs, and this invalidates all birthdays. If there is a large number of users, * invalidation will happen so often that the cache provides no value. * * The class provides a bypass mechanism to handle this situation. *
* public class ActivityThread { * ... * private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery = * new IpcDataCache.QueryHandler<Integer, Birthday>() { * {@literal @}Override * public Birthday apply(Integer) { * return GetService("birthdayd").getUserBirthday(userId); * } * {@literal @}Override * public boolean shouldBypassQuery(Integer userId) { * return userId == NEXT_BIRTHDAY; * } * }; * ... * } ** * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that * particular query. The {@code shouldBypassQuery()} method is not abstract and the default * implementation returns false. * * For security, there is a allowlist of processes that are allowed to invalidate a cache. The * allowlist includes normal runtime processes but does not include test processes. Test * processes must call {@code IpcDataCache.disableForTestMode()} to disable all cache activity in * that process. * * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding. * * To test a binder cache, create one or more tests that exercise the binder method. This should * be done twice: once with production code and once with a special image that sets {@code DEBUG} * and {@code VERIFY} true. In the latter case, verify that no cache inconsistencies are * reported. If a cache inconsistency is reported, however, it might be a false positive. This * happens if the server side data can be read and written non-atomically with respect to cache * invalidation. * * @param
extends PropertyInvalidatedCache.QueryHandler{ /** * Compute a result given a query. The semantics are those of Functor. */ public abstract @Nullable R apply(@NonNull Q query); /** * Return true if a query should not use the cache. The default implementation * always uses the cache. */ public boolean shouldBypassCache(@NonNull Q query) { return false; } }; /** * The list of cache namespaces. Each namespace corresponds to an sepolicy domain. A * namespace is owned by a single process, although a single process can have more * than one namespace (system_server, as an example). * @hide */ @StringDef( prefix = { "MODULE_" }, value = { MODULE_TEST, MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY } ) @Retention(RetentionPolicy.SOURCE) public @interface IpcDataCacheModule { } /** * The module used for unit tests and cts tests. It is expected that no process in * the system has permissions to write properties with this module. * @hide */ @TestApi public static final String MODULE_TEST = PropertyInvalidatedCache.MODULE_TEST; /** * The module used for system server/framework caches. This is not visible outside * the system processes. * @hide */ @TestApi public static final String MODULE_SYSTEM = PropertyInvalidatedCache.MODULE_SYSTEM; /** * The module used for bluetooth caches. * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static final String MODULE_BLUETOOTH = PropertyInvalidatedCache.MODULE_BLUETOOTH; /** * Make a new property invalidated cache. The key is computed from the module and api * parameters. * * @param maxEntries Maximum number of entries to cache; LRU discard * @param module The module under which the cache key should be placed. * @param api The api this cache front-ends. The api must be a Java identifier but * need not be an actual api. * @param cacheName Name of this cache in debug and dumpsys * @param computer The code to compute values that are not in the cache. * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String cacheName, @NonNull QueryHandlercomputer) { super(maxEntries, module, api, cacheName, computer); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi @Override public void disableForCurrentProcess() { super.disableForCurrentProcess(); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static void disableForCurrentProcess(@NonNull String cacheName) { PropertyInvalidatedCache.disableForCurrentProcess(cacheName); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi @Override public @Nullable Result query(@NonNull Query query) { return super.query(query); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi @Override public void invalidateCache() { super.invalidateCache(); } /** * Invalidate caches in all processes that are keyed for the module and api. * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static void invalidateCache(@NonNull @IpcDataCacheModule String module, @NonNull String api) { PropertyInvalidatedCache.invalidateCache(module, api); } /** * This is a convenience class that encapsulates configuration information for a * cache. It may be supplied to the cache constructors in lieu of the other * parameters. The class captures maximum entry count, the module, the key, and the * api. * * There are three specific use cases supported by this class. * * 1. Instance-per-cache: create a static instance of this class using the same * parameters as would have been given to IpcDataCache (or * PropertyInvalidatedCache). This static instance provides a hook for the * invalidateCache() and disableForLocalProcess() calls, which, generally, must * also be static. * * 2. Short-hand for shared configuration parameters: create an instance of this class * to capture the maximum number of entries and the module to be used by more than * one cache in the class. Refer to this instance when creating new configs. Only * the api and (optionally key) for the new cache must be supplied. * * 3. Tied caches: create a static instance of this class to capture the maximum * number of entries, the module, and the key. Refer to this instance when * creating a new config that differs in only the api. The new config can be * created as part of the cache constructor. All caches that trace back to the * root config share the same key and are invalidated by the invalidateCache() * method of the root config. All caches that trace back to the root config can be * disabled in the local process by the disableAllForCurrentProcess() method of the * root config. * * @hide */ public static class Config { private final int mMaxEntries; @IpcDataCacheModule private final String mModule; private final String mApi; private final String mName; /** * The list of cache names that were created extending this Config. If * disableForCurrentProcess() is invoked on this config then all children will be * disabled. Furthermore, any new children based off of this config will be * disabled. The construction order guarantees that new caches will be disabled * before they are created (the Config must be created before the IpcDataCache is * created). */ private ArraySet mChildren; /** * True if registered children are disabled in the current process. If this is * true then all new children are disabled as they are registered. */ private boolean mDisabled = false; public Config(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String name) { mMaxEntries = maxEntries; mModule = module; mApi = api; mName = name; } /** * A short-hand constructor that makes the name the same as the api. */ public Config(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api) { this(maxEntries, module, api, api); } /** * Copy the module and max entries from the Config and take the api and name from * the parameter list. */ public Config(@NonNull Config root, @NonNull String api, @NonNull String name) { this(root.maxEntries(), root.module(), api, name); } /** * Copy the module and max entries from the Config and take the api and name from * the parameter list. */ public Config(@NonNull Config root, @NonNull String api) { this(root.maxEntries(), root.module(), api, api); } /** * Fetch a config that is a child of . The child shares the same api as the * parent and is registered with the parent for the purposes of disabling in the * current process. */ public Config child(@NonNull String name) { final Config result = new Config(this, api(), name); registerChild(name); return result; } public final int maxEntries() { return mMaxEntries; } @IpcDataCacheModule public final @NonNull String module() { return mModule; } public final @NonNull String api() { return mApi; } public final @NonNull String name() { return mName; } /** * Register a child cache name. If disableForCurrentProcess() has been called * against this cache, disable th new child. */ private final void registerChild(String name) { synchronized (this) { if (mChildren == null) { mChildren = new ArraySet<>(); } mChildren.add(name); if (mDisabled) { IpcDataCache.disableForCurrentProcess(name); } } } /** * Invalidate all caches that share this Config's module and api. */ public void invalidateCache() { IpcDataCache.invalidateCache(mModule, mApi); } /** * Disable all caches that share this Config's name. */ public void disableForCurrentProcess() { IpcDataCache.disableForCurrentProcess(mName); } /** * Disable this cache and all children. Any child that is added in the future * will alwo be disabled. */ public void disableAllForCurrentProcess() { synchronized (this) { mDisabled = true; disableForCurrentProcess(); if (mChildren != null) { for (String c : mChildren) { IpcDataCache.disableForCurrentProcess(c); } } } } } /** * Create a new cache using a config. * @hide */ public IpcDataCache(@NonNull Config config, @NonNull QueryHandler computer) { super(config.maxEntries(), config.module(), config.api(), config.name(), computer); } /** * An interface suitable for a lambda expression instead of a QueryHandler. * @hide */ public interface RemoteCall { Result apply(Query query) throws RemoteException; } /** * This is a query handler that is created with a lambda expression that is invoked * every time the handler is called. The handler is specifically meant for services * hosted by system_server; the handler automatically rethrows RemoteException as a * RuntimeException, which is the usual handling for failed binder calls. */ private static class SystemServerCallHandler extends IpcDataCache.QueryHandler { private final RemoteCall mHandler; public SystemServerCallHandler(RemoteCall handler) { mHandler = handler; } @Override public Result apply(Query query) { try { return mHandler.apply(query); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Create a cache using a config and a lambda expression. * @hide */ public IpcDataCache(@NonNull Config config, @NonNull RemoteCall computer) { this(config, new SystemServerCallHandler<>(computer)); } }