Loading core/java/android/app/PropertyInvalidatedCache.java +2 −192 Original line number Diff line number Diff line Loading @@ -54,198 +54,8 @@ 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: * * <pre> * parcelable Birthday { * int month; * int day; * } * interface IUserBirthdayService { * Birthday getUserBirthday(int userId); * } * </pre> * * Suppose the service implementation itself looks like this... * * <pre> * 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); * } * } * </pre> * * ... and we have a client in frameworks (loaded into every app process) that looks * like this: * * <pre> * public class ActivityThread { * ... * public Birthday getUserBirthday(int userId) { * return GetService("birthdayd").getUserBirthday(userId); * } * ... * } * </pre> * * 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. * * PropertyInvalidatedCache is part of a pattern for optimizing this kind of * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client * this way: * * <pre> * public class ActivityThread { * ... * private final PropertyInvalidatedCache.QueryHandler<Integer, Birthday> mBirthdayQuery = * new PropertyInvalidatedCache.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_CACHE_KEY = "cache_key.birthdayd"; * private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new * PropertyInvalidatedCache<Integer, Birthday%>( * BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery); * * public void disableUserBirthdayCache() { * mBirthdayCache.disableForCurrentProcess(); * } * public void invalidateUserBirthdayCache() { * mBirthdayCache.invalidateCache(); * } * public Birthday getUserBirthday(int userId) { * return mBirthdayCache.query(userId); * } * ... * } * </pre> * * 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: * <list> * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}. * Usually, the SELinux rules permit a process to write a system property (and therefore * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that * although the cache can be constructed with any module string, whatever string is chosen must be * consistent with the SELinux configuration. * <ul> The API name can be any string of alphanumeric characters. All caches with the same API * are invalidated at the same time. If a server supports several caches and all are invalidated * in common, then it is most efficient to assign the same API string to every cache. * <ul> The cache name can be any string. In debug output, the name is used to distiguish between * caches with the same API name. The cache name is also used when disabling caches in the * current process. So, invalidation is based on the module+api but disabling (which is generally * a once-per-process operation) is based on the cache name. * </list> * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: * * <pre> * 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(); * } * ... * } * </pre> * * The call to {@code PropertyInvalidatedCache.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 PropertyInvalidatedCache in this idiomatic way introduces no new race conditions. * * PropertyInvalidatedCache 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. * <pre> * 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; * } * }; * ... * } * </pre> * * 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 PropertyInvalidatedCache.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. * This interface is deprecated. New clients should use {@link IpcDataCache} instead. Internally, * that class uses {@link PropertyInvalidatedCache} , but that design may change in the future. * * @param <Query> The class used to index cache entries: must be hashable and comparable * @param <Result> The class holding cache entries; use a boxed primitive if possible Loading core/java/android/os/IpcDataCache.java +19 −5 Original line number Diff line number Diff line Loading @@ -48,6 +48,20 @@ 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. * * Clients should be aware of the following commonly-seen issues: * <ul> * * <li>Client calls will not go through the cache before the first invalidation signal is * received. Therefore, servers should signal an invalidation as soon as they have data to offer to * clients. * * <li>Cache invalidation is restricted to well-known processes, which means that test code cannot * invalidate a cache. {@link #disableForTestMode()} and {@link #testPropertyName} must be used in * test processes that attempt cache invalidation. See * {@link PropertyInvalidatedCacheTest#testBasicCache()} for an example. * * </ul> * * 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 Loading Loading @@ -136,20 +150,20 @@ import java.util.concurrent.atomic.AtomicLong; * 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: * <list> * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}. * <ul> * <li> The system property that controls the cache is named {@code cache_key.<module>.<api>}. * Usually, the SELinux rules permit a process to write a system property (and therefore * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that * although the cache can be constructed with any module string, whatever string is chosen must be * consistent with the SELinux configuration. * <ul> The API name can be any string of alphanumeric characters. All caches with the same API * <li> The API name can be any string of alphanumeric characters. All caches with the same API * are invalidated at the same time. If a server supports several caches and all are invalidated * in common, then it is most efficient to assign the same API string to every cache. * <ul> The cache name can be any string. In debug output, the name is used to distiguish between * <li> The cache name can be any string. In debug output, the name is used to distiguish between * caches with the same API name. The cache name is also used when disabling caches in the * current process. So, invalidation is based on the module+api but disabling (which is generally * a once-per-process operation) is based on the cache name. * </list> * </ul> * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: Loading Loading
core/java/android/app/PropertyInvalidatedCache.java +2 −192 Original line number Diff line number Diff line Loading @@ -54,198 +54,8 @@ 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: * * <pre> * parcelable Birthday { * int month; * int day; * } * interface IUserBirthdayService { * Birthday getUserBirthday(int userId); * } * </pre> * * Suppose the service implementation itself looks like this... * * <pre> * 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); * } * } * </pre> * * ... and we have a client in frameworks (loaded into every app process) that looks * like this: * * <pre> * public class ActivityThread { * ... * public Birthday getUserBirthday(int userId) { * return GetService("birthdayd").getUserBirthday(userId); * } * ... * } * </pre> * * 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. * * PropertyInvalidatedCache is part of a pattern for optimizing this kind of * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client * this way: * * <pre> * public class ActivityThread { * ... * private final PropertyInvalidatedCache.QueryHandler<Integer, Birthday> mBirthdayQuery = * new PropertyInvalidatedCache.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_CACHE_KEY = "cache_key.birthdayd"; * private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new * PropertyInvalidatedCache<Integer, Birthday%>( * BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery); * * public void disableUserBirthdayCache() { * mBirthdayCache.disableForCurrentProcess(); * } * public void invalidateUserBirthdayCache() { * mBirthdayCache.invalidateCache(); * } * public Birthday getUserBirthday(int userId) { * return mBirthdayCache.query(userId); * } * ... * } * </pre> * * 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: * <list> * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}. * Usually, the SELinux rules permit a process to write a system property (and therefore * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that * although the cache can be constructed with any module string, whatever string is chosen must be * consistent with the SELinux configuration. * <ul> The API name can be any string of alphanumeric characters. All caches with the same API * are invalidated at the same time. If a server supports several caches and all are invalidated * in common, then it is most efficient to assign the same API string to every cache. * <ul> The cache name can be any string. In debug output, the name is used to distiguish between * caches with the same API name. The cache name is also used when disabling caches in the * current process. So, invalidation is based on the module+api but disabling (which is generally * a once-per-process operation) is based on the cache name. * </list> * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: * * <pre> * 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(); * } * ... * } * </pre> * * The call to {@code PropertyInvalidatedCache.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 PropertyInvalidatedCache in this idiomatic way introduces no new race conditions. * * PropertyInvalidatedCache 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. * <pre> * 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; * } * }; * ... * } * </pre> * * 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 PropertyInvalidatedCache.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. * This interface is deprecated. New clients should use {@link IpcDataCache} instead. Internally, * that class uses {@link PropertyInvalidatedCache} , but that design may change in the future. * * @param <Query> The class used to index cache entries: must be hashable and comparable * @param <Result> The class holding cache entries; use a boxed primitive if possible Loading
core/java/android/os/IpcDataCache.java +19 −5 Original line number Diff line number Diff line Loading @@ -48,6 +48,20 @@ 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. * * Clients should be aware of the following commonly-seen issues: * <ul> * * <li>Client calls will not go through the cache before the first invalidation signal is * received. Therefore, servers should signal an invalidation as soon as they have data to offer to * clients. * * <li>Cache invalidation is restricted to well-known processes, which means that test code cannot * invalidate a cache. {@link #disableForTestMode()} and {@link #testPropertyName} must be used in * test processes that attempt cache invalidation. See * {@link PropertyInvalidatedCacheTest#testBasicCache()} for an example. * * </ul> * * 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 Loading Loading @@ -136,20 +150,20 @@ import java.util.concurrent.atomic.AtomicLong; * 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: * <list> * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}. * <ul> * <li> The system property that controls the cache is named {@code cache_key.<module>.<api>}. * Usually, the SELinux rules permit a process to write a system property (and therefore * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that * although the cache can be constructed with any module string, whatever string is chosen must be * consistent with the SELinux configuration. * <ul> The API name can be any string of alphanumeric characters. All caches with the same API * <li> The API name can be any string of alphanumeric characters. All caches with the same API * are invalidated at the same time. If a server supports several caches and all are invalidated * in common, then it is most efficient to assign the same API string to every cache. * <ul> The cache name can be any string. In debug output, the name is used to distiguish between * <li> The cache name can be any string. In debug output, the name is used to distiguish between * caches with the same API name. The cache name is also used when disabling caches in the * current process. So, invalidation is based on the module+api but disabling (which is generally * a once-per-process operation) is based on the cache name. * </list> * </ul> * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: Loading