FinalizationRegistryObject.h (10884B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * FinalizationRegistry objects allow a program to register to receive a 9 * callback after a 'target' object dies. The callback is passed a 'held value' 10 * (that hopefully doesn't entrain the target). An 'unregister token' is an 11 * object which can be used to remove multiple previous registrations in one go. 12 * 13 * To arrange this, the following data structures are used: 14 * 15 * +---------------------------------------+-------------------------------+ 16 * | FinalizationRegistry compartment | Target zone / compartment | 17 * | | | 18 * | +------------------------------+ | +------------------+ | 19 * | +--+ FinalizationRegistry | | | Zone | | 20 * | | +---+----------------+---------+ | +---------+--------+ | 21 * | | | | | | | 22 * | | v v | v | 23 * | | +---+---+ +---------+---------+ | +------------+------------+ | 24 * | | |Record | | Registrations | | | FinalizationObservers | | 25 * | | |Vector | | map | | +------------+------------+ | 26 * | | +---+---+ +-------------------+ | | | 27 * | | | | Weak :Records| | | | 28 * | | | | unregister:Vector | | v | 29 * | | | | token : | | +------------+------------+ | 30 * | | | +--------------+----+ | | RecordMap map | | 31 * | | | | | +-------------------------+ | 32 * | | | | | | Target : ObserverList | | 33 * | | | | | | object : | | 34 * | | * v * v | +----+-------------+------+ | 35 * | | +-------------------------+-+ * | | | | 36 * | | | FinalizationRecordObject +<--------------------------+ | 37 * | | +---------------------------+ | | | 38 * | | | Queue +--+ | v | 39 * | | +---------------------------+ | | +----+-----+ | 40 * | | | Held value | | | | Target | | 41 * | | +---------------------------+ | | | GC thing | | 42 * | | | | +----------+ | 43 * | +--------------+ +--------------+ | | 44 * | | | | | 45 * | v v | | 46 * | +----------+---+----------+ | | 47 * | | FinalizationQueueObject | | | 48 * | +-------------------------+ | | 49 * | | | 50 * +---------------------------------------+-------------------------------+ 51 * 52 * A FinalizationRegistry consists of several parts: 53 * - the FinalizationRegistry object that consumers see 54 * - zero or more FinalizationRecordObjects representing registered targets 55 * - a FinalizationQueue containing records for targets that have died, used to 56 * queue and call the cleanup callbacks 57 * - a weakmap tracking unregister tokens and their associated records 58 * 59 * Registering a target with a FinalizationRegistry creates a FinalizationRecord 60 * containing a pointer to the queue and the heldValue. This is added to the 61 * registry's vector of registered targets and also to a linked list of 62 * finalization observers which is used to actually track the target. 63 * 64 * When a target is registered an unregister token may be supplied. If so, this 65 * is also recorded by the registry and is stored in a map of 66 * registrations. They keys of this map are weakly held and do not keep the 67 * unregister token alive. 68 * 69 * When targets are unregistered, the registration is looked up in the weakmap 70 * and the corresponding records are cleared. 71 72 * The finalization observer lists are swept during GC to check for records 73 * associated with dying targets. For such targets the associated record list is 74 * processed and each record is added to the FinalizationQueueObject. At a later 75 * time this causes the client's cleanup callback to be run. 76 */ 77 78 #ifndef builtin_FinalizationRegistryObject_h 79 #define builtin_FinalizationRegistryObject_h 80 81 #include "gc/Barrier.h" 82 #include "gc/FinalizationObservers.h" 83 #include "gc/WeakMap.h" 84 #include "js/GCVector.h" 85 #include "vm/NativeObject.h" 86 87 namespace js { 88 89 class FinalizationRegistryObject; 90 class FinalizationRecordObject; 91 class FinalizationQueueObject; 92 93 using HandleFinalizationRegistryObject = Handle<FinalizationRegistryObject*>; 94 using HandleFinalizationRecordObject = Handle<FinalizationRecordObject*>; 95 using HandleFinalizationQueueObject = Handle<FinalizationQueueObject*>; 96 using RootedFinalizationRegistryObject = Rooted<FinalizationRegistryObject*>; 97 using RootedFinalizationRecordObject = Rooted<FinalizationRecordObject*>; 98 using RootedFinalizationQueueObject = Rooted<FinalizationQueueObject*>; 99 100 // A finalization record: a pair of finalization queue and held value. 101 // 102 // A finalization record represents the registered interest of a finalization 103 // registry in a target's finalization. 104 // 105 // Finalization records created in the 'registered' state but may be 106 // unregistered. This happens when: 107 // - the heldValue is passed to the registry's cleanup callback 108 // - the registry's unregister method removes the registration 109 // 110 // Finalization records are added to a per-zone record map. They are removed 111 // when the record is queued for cleanup, or if the interest in finalization is 112 // cancelled. See FinalizationObservers::shouldRemoveRecord for the possible 113 // reasons. 114 115 class FinalizationRecordObject : public gc::ObserverListObject { 116 enum { 117 QueueSlot = ObserverListObject::SlotCount, 118 HeldValueSlot, 119 DebugStateSlot, // Used for assertions only. 120 SlotCount 121 }; 122 123 public: 124 enum State { Unknown, InRecordMap, InQueue }; 125 126 static const JSClass class_; 127 128 static FinalizationRecordObject* create(JSContext* cx, 129 HandleFinalizationQueueObject queue, 130 HandleValue heldValue); 131 132 FinalizationQueueObject* queue() const; 133 Value heldValue() const; 134 bool isRegistered() const; 135 136 #ifdef DEBUG 137 void setState(State state); 138 State getState() const; 139 bool isInRecordMap() const { return getState() == InRecordMap; } 140 bool isInQueue() const { return getState() == InQueue; } 141 #endif 142 143 void setInRecordMap(bool newValue); 144 void setInQueue(bool newValue); 145 void clear(); 146 147 private: 148 static const JSClassOps classOps_; 149 150 static void finalize(JS::GCContext* gcx, JSObject* obj); 151 }; 152 153 using FinalizationRecordVector = 154 GCVector<HeapPtr<FinalizationRecordObject*>, 1, js::CellAllocPolicy>; 155 156 // The JS FinalizationRegistry object itself. 157 class FinalizationRegistryObject : public NativeObject { 158 enum { QueueSlot = 0, RegistrationsSlot, RecordsWithoutTokenSlot, SlotCount }; 159 160 public: 161 using RegistrationsMap = 162 GCHashMap<HeapPtr<Value>, FinalizationRecordVector, gc::WeakTargetHasher>; 163 164 static const JSClass class_; 165 static const JSClass protoClass_; 166 167 FinalizationQueueObject* queue() const; 168 RegistrationsMap* registrations() const; 169 FinalizationRecordVector* recordsWithoutToken() const; 170 171 void traceWeak(JSTracer* trc); 172 173 static bool unregisterRecord(FinalizationRecordObject* record); 174 175 static bool cleanupQueuedRecords(JSContext* cx, 176 HandleFinalizationRegistryObject registry, 177 HandleObject callback = nullptr); 178 179 private: 180 static const JSClassOps classOps_; 181 static const ClassSpec classSpec_; 182 static const JSFunctionSpec methods_[]; 183 static const JSPropertySpec properties_[]; 184 185 static bool construct(JSContext* cx, unsigned argc, Value* vp); 186 static bool register_(JSContext* cx, unsigned argc, Value* vp); 187 static bool unregister(JSContext* cx, unsigned argc, Value* vp); 188 static bool cleanupSome(JSContext* cx, unsigned argc, Value* vp); 189 190 static bool addRegistration(JSContext* cx, 191 HandleFinalizationRegistryObject registry, 192 HandleValue unregisterToken, 193 HandleFinalizationRecordObject record); 194 static void removeRegistrationOnError( 195 HandleFinalizationRegistryObject registry, HandleValue unregisterToken, 196 HandleFinalizationRecordObject record); 197 198 static bool preserveDOMWrapper(JSContext* cx, HandleObject obj); 199 200 static void trace(JSTracer* trc, JSObject* obj); 201 static void finalize(JS::GCContext* gcx, JSObject* obj); 202 }; 203 204 // Contains information about the cleanup callback and the records queued to 205 // be cleaned up. This is not exposed to content JS. 206 class FinalizationQueueObject : public NativeObject { 207 enum { 208 CleanupCallbackSlot = 0, 209 HostDefinedDataSlot, 210 RecordsToBeCleanedUpSlot, 211 IsQueuedForCleanupSlot, 212 DoCleanupFunctionSlot, 213 HasRegistrySlot, 214 SlotCount 215 }; 216 217 enum DoCleanupFunctionSlots { 218 DoCleanupFunction_QueueSlot = 0, 219 }; 220 221 public: 222 static const JSClass class_; 223 224 JSObject* cleanupCallback() const; 225 JSObject* getHostDefinedData() const; 226 bool hasRecordsToCleanUp() const; 227 FinalizationRecordVector* recordsToBeCleanedUp() const; 228 bool isQueuedForCleanup() const; 229 JSFunction* doCleanupFunction() const; 230 bool hasRegistry() const; 231 232 void queueRecordToBeCleanedUp(FinalizationRecordObject* record); 233 void setQueuedForCleanup(bool value); 234 235 void setHasRegistry(bool newValue); 236 237 static FinalizationQueueObject* create(JSContext* cx, 238 HandleObject cleanupCallback); 239 240 static bool cleanupQueuedRecords(JSContext* cx, 241 HandleFinalizationQueueObject registry, 242 HandleObject callback = nullptr); 243 244 private: 245 static const JSClassOps classOps_; 246 247 static bool doCleanup(JSContext* cx, unsigned argc, Value* vp); 248 249 static void trace(JSTracer* trc, JSObject* obj); 250 static void finalize(JS::GCContext* gcx, JSObject* obj); 251 }; 252 253 } // namespace js 254 255 #endif /* builtin_FinalizationRegistryObject_h */