1 // Copyright 2020 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.os.Handler; 8 import android.os.Looper; 9 10 import androidx.annotation.NonNull; 11 import androidx.annotation.Nullable; 12 import androidx.annotation.VisibleForTesting; 13 14 import org.chromium.base.lifetime.DestroyChecker; 15 16 import java.lang.ref.WeakReference; 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Set; 20 21 /** 22 * UnownedUserDataHost is a type-safe and heterogeneous container that does not own the objects that 23 * are stored within. It has the ability to associate a key of type {@code UnownedUserDataKey<T>}, 24 * where {@code T extends UnownedUserData}, with an instance of {@code T}. 25 * <p> 26 * Mismatch of types between key and value type information can be checked at compile time, which 27 * ensures it is not possible to insert or retrieve data where the types do not match. Neither the 28 * key nor the object is allowed to be {@code null}. 29 * <p> 30 * Value objects are held using {@link WeakReference} in the container, which means that they can be 31 * garbage collected once the last strong reference has been removed. The {@link UnownedUserDataKey} 32 * is still a strong reference, so it is important that it does not have a reference to the object 33 * it is used as a key for. When trying to retrieve a garbage collected item for which a key is 34 * still held, the entry in the map is removed during the invocation. 35 * <p> 36 * Invoking {@link #destroy()} clears out the map, including both keys and the {@link 37 * WeakReference}s to the {@link UnownedUserData}s, making them available for garbage collection. 38 * This enables the garbage collector to not be blocked on this class for continuing the garbage 39 * collection cycle. During this process, all {@link UnownedUserData} objects are informed that they 40 * have been detached. 41 * <p> 42 * All interaction with the UnownedUserDataHost must be performed on the same thread. 43 * <p> 44 * {@link UnownedUserData} is somewhat similar to {@link org.chromium.base.UserData}, except that it 45 * is not owned by the host. The structure is also a bit different since the instances are retrieved 46 * through a {@link UnownedUserDataKey} instead of the class type itself. The reason for this is to 47 * ensure that we protect against accidental incorrect usage where something has been made 48 * accessible through misconfigured GN visibility rules, incorrect package visibility or 49 * misconfigured DEPS rules. In addition, it enforces clients to go through the from-method to 50 * retrieve the object, ensuring that control stays with the object itself. 51 * <p> 52 * All methods on UnownedUserDataHost except {@link #destroy()} is package protected to ensure all 53 * interaction with the host goes through the {@link UnownedUserDataKey}. 54 * <p> 55 * Example usage: 56 * <pre>{@code 57 * public class HolderClass { 58 * // Defines the container. 59 * private final UnownedUserDataHost mUnownedUserDataHost = new UnownedUserDataHost(); 60 * 61 * public UnownedUserDataHost getUnownedUserDataHost() { 62 * return mUnownedUserDataHost; 63 * } 64 * } 65 * 66 * public class Foo implements UnownedUserData { 67 * // Keeping KEY private enforces acquisition by calling #from(), therefore Foo is in control 68 * // of getting the instance. 69 * private static final UnownedUserDataKey<Foo> KEY = new UnownedUserDataKey<>(Foo.class); 70 * 71 * // The UnownedUserData framework enables this method in particular. 72 * public static Foo from(HolderClass holder) { 73 * return KEY.retrieveDataFromHost(holderClass.getUnownedUserDataHost()); 74 * } 75 * 76 * public void initialize(HolderClass holderClass) { 77 * // This could also be in the constructor or somewhere else that is reasonable for a 78 * // particular object. 79 * KEY.attachToHost(holderClass.getUnownedUserDataHost(), this); 80 * } 81 * 82 * public void destroy() { 83 * // This ensures that the UnownedUserData can not be resurrected through any 84 * // UnownedUserDataHost after this. 85 * // For detaching from a particular host, use KEY.detachFromHost(host) instead. 86 * KEY.detachFromAllHosts(this); 87 * } 88 * } 89 * 90 * 91 * // After construction, `foo` needs to attach itself to the HolderClass instance of the 92 * // UnownedUserDataHost. 93 * // Depending on who owns Foo, this could be its factory, or some other ownership model. Foo 94 * // does not need to hold on to the HolderClass, as that is taken care of by the key during 95 * // attachment. It is up to the implementor to decide whether this is in the constructor, or 96 * // in a separate initialize step. 97 * Foo foo = new Foo(); 98 * foo.initialize(holderClass); 99 * 100 * ... 101 * 102 * // Now that the instance of Foo is attached to the particular instance of Holder, it 103 * // can be retrieved using just the HolderClass instance. 104 * Foo sameFoo = Foo.from(holderClass); 105 * 106 * ... 107 * 108 * // During destruction of `foo`, it must remove itself from the instance of HolderClass to 109 * // ensure that it can not be retrieved using that path any longer. 110 * foo.destroy(); 111 * } 112 * </pre> 113 * <p> 114 * The code snippet above uses a {@code static} key to be able to facilitate the method {@code 115 * public static Foo from(HolderClass holderClass)}, since it would not be possible to retrieve the 116 * private key from that method if it was an instance member. 117 * <p> 118 * The code snippet above also assumes that {@code Foo} has knowledge about the {@code HolderClass}, 119 * instead of taking in a {@link UnownedUserDataHost} in the {@code from} method, since that 120 * typically provides a more pleasant experience for users. 121 * <p> 122 * There is also another common pattern for retrieving an attached object, and that is to do it 123 * lazily: 124 * <pre>{@code 125 * public static Foo from(HolderClass holderClass) { 126 * Foo foo = KEY.retrieveDataFromHost(holderClass.getUnownedUserDataHost()); 127 * if (foo == null) { 128 * foo = new Foo(); 129 * KEY.attachToHost(holderClass.getUnownedUserDataHost(), foo); 130 * } 131 * return foo; 132 * } 133 * } 134 * </pre> 135 * <p> 136 * However, it is important to note that in this scenario, as soon as the code that invokes 137 * from(...) drops the reference, Foo will be eligible for garbage collection since the host only 138 * holds a {@link WeakReference}. This means that Foo could end up being constructed and garbage 139 * collected often, depending on whether the caller holds on to a strong reference or not. 140 * 141 * @see UnownedUserDataKey for information about the type of key that is required. 142 * @see UnownedUserData for the marker interface used for this type of data. 143 */ 144 public final class UnownedUserDataHost { retrieveNonNullLooperOrThrow()145 private static Looper retrieveNonNullLooperOrThrow() { 146 Looper looper = Looper.myLooper(); 147 if (looper == null) throw new IllegalStateException(); 148 return looper; 149 } 150 151 private final ThreadUtils.ThreadChecker mThreadChecker = new ThreadUtils.ThreadChecker(); 152 private final DestroyChecker mDestroyChecker = new DestroyChecker(); 153 154 /** 155 * Handler to use to post {@link UnownedUserData#onDetachedFromHost(UnownedUserDataHost)} 156 * invocations to. 157 */ 158 private Handler mHandler; 159 160 /** The core data structure within this host. */ 161 private HashMap<UnownedUserDataKey<?>, WeakReference<? extends UnownedUserData>> 162 mUnownedUserDataMap = new HashMap<>(); 163 UnownedUserDataHost()164 public UnownedUserDataHost() { 165 this(new Handler(retrieveNonNullLooperOrThrow())); 166 } 167 168 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) UnownedUserDataHost(Handler handler)169 /* package */ UnownedUserDataHost(Handler handler) { 170 mHandler = handler; 171 } 172 173 /** 174 * Stores a {@link WeakReference} to {@code object} using the given {@code key}. 175 * 176 * <p>If the key is already attached to a different host, it is detached from that host. 177 * 178 * @param key the key to use for the object. 179 * @param newValue the object to store. 180 * @param <T> the type of {@link UnownedUserData}. 181 */ set( @onNull UnownedUserDataKey<T> key, @NonNull T newValue)182 /* package */ <T extends UnownedUserData> void set( 183 @NonNull UnownedUserDataKey<T> key, @NonNull T newValue) { 184 checkState(); 185 186 // If we already have data, we might want to detach that first. 187 if (mUnownedUserDataMap.containsKey(key)) { 188 T currentValue = get(key); 189 // If we are swapping objects, inform the previous object of detachment. 190 if (!newValue.equals(currentValue)) key.detachFromHost(this); 191 } 192 193 mUnownedUserDataMap.put(key, new WeakReference<>(newValue)); 194 } 195 196 /** 197 * Retrieves the {@link UnownedUserData} object stored under the given key. 198 * 199 * @param key the key to use for the object. 200 * @param <T> the type of {@link UnownedUserData}. 201 * @return the stored version or {@code null} if it is not stored or has been garbage collected. 202 */ 203 @Nullable get(@onNull UnownedUserDataKey<T> key)204 /* package */ <T extends UnownedUserData> T get(@NonNull UnownedUserDataKey<T> key) { 205 checkState(); 206 207 WeakReference<? extends UnownedUserData> valueWeakRef = mUnownedUserDataMap.get(key); 208 if (valueWeakRef == null) return null; 209 UnownedUserData value = valueWeakRef.get(); 210 if (value == null) { 211 // The object the entry referenced has now been GCed, so remove the entry. 212 key.detachFromHost(this); 213 return null; 214 } 215 return key.getValueClass().cast(value); 216 } 217 218 /** 219 * Removes the {@link UnownedUserData} object stored under the given key, if any. 220 * 221 * @param key the key to use for the object. 222 * @param <T> the type of {@link UnownedUserData}. 223 */ remove(@onNull UnownedUserDataKey<T> key)224 /* package */ <T extends UnownedUserData> void remove(@NonNull UnownedUserDataKey<T> key) { 225 checkState(); 226 227 WeakReference<? extends UnownedUserData> valueWeakRef = mUnownedUserDataMap.remove(key); 228 if (valueWeakRef == null) return; 229 230 UnownedUserData value = valueWeakRef.get(); 231 // Invoking anything on `value` might be re-entrant for the caller so responses should be 232 // posted. However, the informOnDetachmentFromHost() method contains a documented warning 233 // that it might be re-entrant, so it is OK to use that to guard the call to 234 // `onDetachedFromHost(...)`. 235 if (value != null && value.informOnDetachmentFromHost()) { 236 mHandler.post(() -> value.onDetachedFromHost(this)); 237 } 238 } 239 240 /** 241 * Destroys the UnownedUserDataHost by clearing out the map, making the objects stored within 242 * available for garbage collection as early as possible, in case the object owning the 243 * UnownedUserDataHost stays alive for a while after destroy() has been invoked. 244 * <p> 245 * Objects stored within the UnownedUserDataHost are informed of this destroy() call through 246 * {@link UnownedUserData#onDetachedFromHost(UnownedUserDataHost)}, and the {@link 247 * UnownedUserDataKey} instances are updated to not refer to this host anymore. 248 */ destroy()249 public void destroy() { 250 mThreadChecker.assertOnValidThread(); 251 252 // Protect against potential races. 253 if (mDestroyChecker.isDestroyed()) return; 254 255 // Create a shallow copy of all keys to ensure each held object can safely remove itself 256 // from the map while iterating over their keys. 257 Set<UnownedUserDataKey<?>> keys = new HashSet<>(mUnownedUserDataMap.keySet()); 258 for (UnownedUserDataKey<?> key : keys) key.detachFromHost(this); 259 260 mUnownedUserDataMap = null; 261 mHandler = null; 262 263 // Need to wait until the end to destroy the ThreadChecker to ensure that the 264 // detachFromHost(...) invocations above are allowed to invoke remove(...). 265 mDestroyChecker.destroy(); 266 } 267 268 @VisibleForTesting(otherwise = VisibleForTesting.NONE) getMapSize()269 /* package */ int getMapSize() { 270 checkState(); 271 272 return mUnownedUserDataMap.size(); 273 } 274 isDestroyed()275 /* package */ boolean isDestroyed() { 276 return mDestroyChecker.isDestroyed(); 277 } 278 checkState()279 private void checkState() { 280 mThreadChecker.assertOnValidThread(); 281 mDestroyChecker.checkNotDestroyed(); 282 } 283 } 284