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

Commit 600835de authored by Lee Shombert's avatar Lee Shombert Committed by Android (Google) Code Review
Browse files

Merge "Update IpcDataCache documentation" into main

parents f8f24c07 b99a0255
Loading
Loading
Loading
Loading
+2 −192
Original line number Diff line number Diff line
@@ -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&lt;Integer, Birthday%&gt; mUidToBirthday;
 *   {@literal @}Override
 *   public synchronized Birthday getUserBirthday(int userId) {
 *     return mUidToBirthday.get(userId);
 *   }
 *   private synchronized void updateBirthdays(Map&lt;Integer, Birthday%&gt; 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&lt;Integer, Birthday&gt; mBirthdayQuery =
 *       new PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt;() {
 *           {@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&lt;Integer, Birthday%&gt; mBirthdayCache = new
 *     PropertyInvalidatedCache&lt;Integer, Birthday%&gt;(
 *             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&lt;Integer, Birthday%&gt; 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&lt;Integer, Birthday&gt; mBirthdayQuery =
 *       new IpcDataCache.QueryHandler&lt;Integer, Birthday&gt;() {
 *           {@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
+19 −5
Original line number Diff line number Diff line
@@ -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
@@ -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: