• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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